Sunday, 22 April 2012

libavformat_p1: A first look at libavutil's memory allocator

   libavformat is one of the libraries that make up the FFmpeg project. FFmpeg is one of the leading mutimedia frameworks out right now and is used  in lot of different projects . It's used in VLCMPlayerHandBrakeBlenderGoogle Chrome and many others. libavformat, in particular, is a muxing/demuxing library which means it's responsible for reading and separating streams of data from within a/v containers. My interest is isn't in creating some of kind a/v related application using FFmpeg or libavformat, but instead I'm just auditing libavformat's code base. Why? Because it's fun. :)


   Now within FFmpeg, there's a library called libavutil. libavutil contains a number of general utilities which are used by various libraries within the project itself, including libavformat. Memory allocation undoubtedly plays a big part in these kinds of libraries and the memory allocator used in libavformat is actually included from libavutil. So I'm tackling this part of the library first.



   So here is the the bread and butter of the memory allocator. Immediately, you can notice that there's some conditional compilation going on.


  • CONFIG_MEMALIGN_HACK
  • HAVE_POSIX_MEMALIGN
  • HAVE_MEMALIGN

 CONFIG_MEMALIGN_HACK is only used when the OS doesn't have one of the two supported memalign functions. So in this scenario, the memory alignment is done manually. HAVE_MEMALIGN and HAVE_POSIX_MEMALIGN are similar. HAVE_POSIX_MEMALIGN takes precedence over HAVE_MEMALIGN since posix_memalign() is more compliant on systems that have certain alignment restrictions. For all intents and purposes, posix_memalign() and memalign() can just be thought of as two functions that return pointers to blocks of memory which are aligned on the boundaries specified.

Now with that aside, we can take a closer look at the code.


Here we see that it's doing a check to make sure that the size is at least 32 bytes less than the maximum size of data allowed to be allocated. The first question one should ask when seeing this is, why 32 bytes? The answer is memory alignment. ALIGN is defined as either 16 or 32 depending on whether or not the processor supports AVX instructions. Here, it seems, they got lazy and instead of just checking to see whether it was 16 or 32, they just assumed the larger. So if the size requested was too large, it just returns NULL and that's the end of it. However, if the size wasn't too large, things become more interesting. Now the memory is allocated and aligned. If HAVE_MEMALIGN or HAVE_POSIX_MEMALIGN is defined then the corresponding function is called. However, when CONFIG_MEMALIGN_HACK is defined, some interesting code is run.



  The first thing this section of code does is that it makes a call to malloc() and allocates size+ALIGN bytes of memory. From our previous observations it should be obvious that the extra bytes allocated is so that alignment can take place. Now if the allocation fails, ptr is set to NULL and the value of ptr is returned. 

  
  Now let's look at what happens if the allocation was successful. It sets diff to the result of an expression containing ptr and ALIGN that involves some bitwise operations. The first part of the expression is ((-(long)ptr - 1). Now the '-' operator at the front tells us that it's computing the negative equivalent of ptr. Now, thinking about that in terms of bits and integer representation, it finds the two's complement of the original value. In other words, it flips all of the bits and then adds one. This is immediately followed by the subtraction of one which effectively cancels out the addition at the end of the two's complement, making it the one's complement of the original value. i.e All the bits were flipped. 


   After all of that occurs, it performs a bitwise AND against ALIGN-1. Keeping in mind that ALIGN is a power of 2, this means that all the bits { b_0, b_1, ... , b_n } before b_x where b^x = ALIGN are being considered during the bitwise AND. Now since the bits being considered against the bitwise AND are the ones before b_x in ptr that are 0 ( since the bits were flipped ), we get the  sum needed to be added to ptr to cause the bits before b_x to add up to ALIGN-1. Now to that sum, 1 is added. We now have the sum necessary to increase the pointer to a multiple of ALIGN. The result of the expression is stored in diff, and then diff is added to ptr.  Now this alignment is guaranteed to increase ptr by at least 1 byte, so it's perfectly safe in the next line when they store the value of diff at an offset of -1 from ptr. In case you're wondering why this is done, it's so that when freeing the memory later on, and the original value returned from malloc() is needed, you can compute it by subtracting diff from the value of ptr.

Saturday, 21 April 2012

Fiddling with ASN.1/DER and X.509 Certificates

   So it turns out that one of the most popular formats, if not THE most popular format, for storing X.509 certificates is base64 encoded DER; DER being an acronym for Distinguished Encoding Rules which is a concrete derivative of ASN.1 ( Abstract Syntax Notation One ). Well after a few days of reading about these two things and how they are related, I'm surprised at how little I'm actually able to understand. There is a fair amount of documentation about ASN.1 and a fair amount about X.509 certificates but there seems to be very little about how they relate. It also doesn't help that what little does exist is extremely vague...

  Luckily for me, I seemed to have stumbled upon ( literally stumbled, not the service ), two articles which seem to discuss both and how they intertwine. It is unfortunate that both articles are extremely long but maybe their density will prove to be a time saver in the long run.


  Now I can truly appreciate the value of .NET. They've simplified the entire process down to a single function call. I don't like the fact that I don't understand exactly what's going on but I have deadlines so it'll have to do for now. I hope to start my own   decoder implementation soon and hopefully finish it within the month.

Tuesday, 17 April 2012

Recursion

What is Recursion?

   Recursion is an algorithmic method for solving problems where the problem itself consists of a number of sub-problems that are near identical, but smaller to itself. These sub-problems, however, must eventually reach a base case.( Else we just end up trying to solve an infinite number of sub-problems which will probably just end up in the process's stack space being exhausted ) Since the method involves solving similar, smaller sub-problems, it shouldn't be surprising that recursion involves a function calling itself with modified arguments.
  1. Recursion involves solving a problem by solving similar but smaller sub-problems.
  2. There must be a base case, so at some point, the decomposition of problems into sub-problems will stop.
  3. Recursive solutions to problems involve functions calling themselves with modified arguments.

Note: This isn't the only situation in which recursion is applicable and this isn't the only way to think about recursion. In my opinion, this is just the best way to introduce the concept.

   Finding the nth number of the Fibonacci sequence is an easy way to demonstrate how recursion works. Since recursion involves solving a problem by solving similar sub-problems, then it would seem logical that the function would call itself.


 In the Fibonacci sequence, each term is the result of the sum of the previous two terms. The exception to this would be the first two terms where they are both 1.


 Let's say we wanted to find the nth number of the Fibonacci sequence. If n was 1 or 2, then we would just return the number, 1. However, if n > 2, then we would return the sum of the two previous fibonacci numbers, Fib(n-1) and Fib(n-2).


Fib(n) is the original problem. We find it's solution by solving similar, smaller sub-problems, Fib(n-1) and Fib(n-2).


In this code snippet, we can clearly see all the characteristics of a recursive solution.
  1. The problem of finding the nth Fibonacci number is computed using the solutions to similar sub-problems;  Fib(n-1), Fib(n-2)
  2. The base case exists when n=1,2 for Fib(n)
  3. The Fib function indeed does call itself with modified arguments.