4

While reading Hacking: The Art of Exploitation (a wonderful book!), I came across this function:

void binary_print(unsigned int value) {
   unsigned int mask = 0xff000000; // Start with a mask for the highest byte.
   unsigned int shift = 256*256*256; // Start with a shift for the highest byte.
   unsigned int byte, byte_iterator, bit_iterator;

   for(byte_iterator=0; byte_iterator < 4; byte_iterator++) {
      byte = (value & mask) / shift; // Isolate each byte.
      printf(" ");
      for(bit_iterator=0; bit_iterator < 8; bit_iterator++) { // Print the byte's bits.
         if(byte & 0x80) // If the highest bit in the byte isn't 0,
            printf("1");       // print a 1.
         else
            printf("0");       // Otherwise, print a 0.
         byte *= 2;         // Move all the bits to the left by 1.
      }
      mask /= 256;       // Move the bits in mask right by 8.
      shift /= 256;      // Move the bits in shift right by 8.
   } 
}

Here's a input-output table for the function:

= = = = = = = = = = = = = = = = = = = = = =
INPUT : OUTPUT
= = = = = = = = = = = = = = = = = = = = = =
0     : 00000000 00000000 00000000 00000000
2     : 00000000 00000000 00000000 00000010
1     : 00000000 00000000 00000000 00000001
1024  : 00000000 00000000 00000100 00000000
512   : 00000000 00000000 00000010 00000000
64    : 00000000 00000000 00000000 01000000
= = = = = = = = = = = = = = = = = = = = = =

By this I know that binary_print() converts decimal to binary.

But I don't understand how exactly the function finds the right answer. Specifically:

  • What is mask? How did the author arrive at the value 0xff000000? (0xff000000 seems to close in towards 2^32, the maximum value of int in the system)
  • What is shift? Why initialize it to 256^3? (To me, seems like it has something to do with the place weights in hexadecimal)
  • What actually happens in these lines:
    • byte = (value & mask)/shift
    • byte & 0x80

In short, I would like to understand the method binary_print() uses to do the conversion.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
TachaVinci
  • 63
  • 5
  • 2
    Is there any @e-satis in the C world too:) ?(http://stackoverflow.com/users/9951/e-satis). For an example, this question needs an answer like the answer on python yield keyword - http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained/231855#231855 – Yavar Nov 21 '13 at 06:21
  • See MSDN: [C Bitwise Operators](http://msdn.microsoft.com/en-us/library/17zwb64t.aspx), and [Bitwise Shift Operators](http://msdn.microsoft.com/en-us/library/f96c63ed.aspx) And Wikipedia: [Counting in binary](http://en.wikipedia.org/wiki/Binary_number#Counting_in_binary) :) – parrowdice Nov 21 '13 at 06:01

3 Answers3

6

As a bit mask (on 32 bits machines) 0xff000000 has all 1 bits for the highest byte. And shift is initialized to 0x1000000 (i.e. 256*256*256).

byte = (value & mask)/shift is using bit-and to extract the bits of the mask then shifting them to get a value between 0 and 0xff (fitting in an 8 bit byte).

Notice that for unsigned numbers mask /= 256 is the same as mask = mask >> 8 (and the compiler generally optimizes the first to the second).

The best way to understand exactly what is happening could be to compile your code with all warnings and debugging info (e.g. gcc -Wall -g) and to use the debugger (gdb) to run your program step by step and display relevant state.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
3

This is more of a comment than an answer. I haven't seen the book in question, so I have no idea what that code excerpt is supposed to demonstrate, but it's a highly over-elaborated way of producing the desired output.

Here's a simpler one:

void binary_print(unsigned value) {
  for (unsigned mask = 0x80000000;  // High order bit
       mask;                        // While we have a bit
       mask >>= 1) {                // Shift right to next bit position
    if (value & mask) putchar('1'); // Bit is set: print a 1
    else              putchar('0'); // Bit is not set: print a 0
    if (mask & 0x01010100) putchar(' '); // A space if we just did the
                                         // low order bit of some byte
  }
}

Aside from the hack in the last line, which is not too bizarre, I hope, I think the logic should be pretty simple for anyone familiar with bit operations.printf is overkill

rici
  • 234,347
  • 28
  • 237
  • 341
0

We take O_APPEND as an example with a value of 1024 or 0x400:

If you look at the values in the byte_iterator loop you can watch the mask- and shift values changing:

byte_iterator: 0, mask: ff000000, shift: 1000000, byte: 0
byte_iterator: 1, mask: ff0000,   shift: 10000,   byte: 0
byte_iterator: 2, mask: ff00,     shift: 100,     byte: 4
byte_iterator: 3, mask: ff,       shift: 1,       byte: 0

Combining mask and value with a bitwise AND returns the byte where the mask is 'ff', means the first loop gives the highest byte, the last loop returns the lowest byte. In this example we have O_APPEND with a hex-value of 00 00 04 00 giving the third byte is 4, all other bytes are 0.

In the bit_iterator loop the mask used is 0x80 or binary 00001000:

loop number: 0, byte: 4,   byte & 0x80: 0
loop number: 1, byte: 8,   byte & 0x80: 0
loop number: 2, byte: 16,  byte & 0x80: 0
loop number: 3, byte: 32,  byte & 0x80: 0
loop number: 4, byte: 64,  byte & 0x80: 0
loop number: 5, byte: 128, byte & 0x80: 80
loop number: 6, byte: 256, byte & 0x80: 0
loop number: 7, byte: 512, byte & 0x80: 0

The 6th loop returns a value != 0, so the byte is printed out as 00000100 equals 0x4.

Hagen
  • 1
  • 2