简体   繁体   中英

Assembly x86 MASM - Problem of extracting 5 bits from memory

I have a problem to solve and I have no idea on how to go about it. I'm asking for a general idea on how to go about this. I have a memory address, in ESI. Memory represents some kind of simplified ASCII encoding, where 5 bits, one after another mean a specific character. The memory ends with five bits - 00000b. In order to convert to normal 8-bit ASCII 60H has to be added to each 5-bit value. I would want to store each 5-bit encoded character as 8-bit ASCII encoded under address EDI. EDI also has to end with 0 - 00000000b.

Example: [ESI] = 00011 00010 11010 00000b [EDI] = 01100011 01100010 01111010 00000000b

How i would go about extracting each 5 bits, one after another from esi?

First and foremost, you need to determine how the 5-bit codes are arranged within the input byte stream.

We need to start with whole input data bytes: if we say that [ESI] = 00011 00010 11010 00000, we are skipping over an important first step in the interpretation and decoding of the 8-bit bytes.

Just like with endian-ness, there are two sensible ways this can be done. One of them is more appropriate for the intel instruction set.

 76543210   bit numbering position
+--------+
|        |      first input data byte
+--------+
    .....    (A) possible position of 5-bit code within first input byte
 .....       (B) alternate position of 5-bit code within first input byte

Choice (A) says that the first 5-bit code is located in least significant bits of the first input data byte, while choice (B) says that the first 5-bit code is located in the most significant bits of the first input data byte.

As the intel machine is little endian and numbers bits from LSB to MSB, choice (A) is more natural on that processor.

Choice (A) would view the input byte stream as follows:

    1       2        3         input byte stream byte number
76543210 76543210 76543210     input byte bit position numbers
hgfedcba ponmlkji xwvutsrq     input byte stream, individual bits as letters
22211111 43333322 55554444     5-bit code number for each bit
   /   /   \  \
  /   /     |  |
 /   /      |  |
/   /       |  |
edcba jihgf onmlk tsrqp yxwvu  5-bit code output stream
  1     2     3     4     5    5-bit code output number

You can see that bits for 5-bit code #2 cross a byte boundary, as does for 5-bit code #4.

With Choice (B) the view would look like this:

    1       2        3         input byte stream byte number
76543210 76543210 76543210     input byte bit position numbers
abcdefgh ijklmnop qrstuvwx     input byte stream, individual bits as letters
11111222 22333334 44445555     5-bit code number for each bit
|   | \   |\   \\    \
|   | |   | \   \\    |
|   | |   | |   | \   |
abcde fgjij klmno pqrst uvwxy  5-bit code output stream
  1     2     3     4     5    5-bit code output number

This is more like what you're showing in your text, so you really need to figure out which choice to use. A concrete example will show where within the first data byte that the first 5-bit code is located.


Either way, one approach is to take in 8-bit bytes and put out 5-bit codes.

The way to deal with the size mismatch is to use a bit queue (variable length) as a buffer, and conditionally take a whole input byte into the bit queue whenever the bit count in the bit queue is less than 5.

(This is a buffering approach; buffering is used everywhere, such as to satisfy size mismatches when an application wants to stream in or out characters/bytes (think printf , and lines of text) yet the device, like file system, wants to write blocks (chunks of 4096). )

As such, the size of the bit_queue is 4 (the largest value not enough to make a 5-bit code) + 8 (a whole new byte) = 12 bits in the queue at maximum — so, the bit_queue will fit in a 16-bit register, and also, we will need another variable as bit count (max value 12).

Basically, we can have a loop that, for each iteration, always outputs one 5-bit code, and conditionally consumes one 8-bit input data byte (eg when needed). This approach will keep the queue supplied with enough bits to output a 5-bit code every iteration.


More formally:

    int bit_count = 0;
    uint_16 bit_queue = 0;

loop:
    if bit_count < 5 then          // need to add more bits? so get a byte
        temp8 = *sourcePointer++   // byte whole fetch from memory
        temp16 = temp8             // zero extend temp8 to 16 bits
                                   //  to match the size of bit_queue
        bit_queue = merge(bit_queue, temp16)
        bit_count += 8             // we just added 8 bits to the bit queue
    endif
    temp5 = extract5(bit_queue)    // take the 5 bits of interest
    bit_queue = remove5(bit_queue) // discard 5 bit code from the bit queue
    bit_count -= 5                 // we just removed 5 bits from bit queue
    *destinationPointer++ = temp5 + 0x60
    if temp5 != 0 goto loop

I have written merge, extract5, and remove5 as functions in that pseudo code but they are simple Shift , Or , and Mask logical operations to be done inline (rather than calling functions).

The reason for showing them more abstractly as functions is that their exact definition depends on choice (A) vs. (B).

For choice (A), the new 8 bits from each next input data byte are positioned at the high (more significant) side of the queue, and 5-bit codes are removed from the LSB portion of the bit queue, whereas for choice (B) the new 8 bits from each next input data byte are positioned at the low/LSB side of the queue and 5-bit codes are removed from the MSB portion of the bit queue.


Example using choice (A):

Initially, the bit_queue is empty and bit_count is zero. Thus, the conditional operation to enter an input data byte into the bit_queue simply loads the bit_queue with first data byte (the merge operation merges with the empty queue).

  00000000hgfedcba     bit_queue loaded with first byte

                 8     bit_count

Function extract5 . The 5-bit code is simply the mask of the bit_queue with 0x1f, yielding

  00000000hgfedcba     bit_queue loaded with first byte
  0000000000011111     mask, 0x1f     
 ----------------- &   mask operation
  00000000000edcba     5-bit code result

We can add the 0x60 and send this to the output data stream.

Next function remove5 :

  bit_queue >>= 5      discard 5 bits by shifting them off the end

  00000000hgfedcba     bit_queue: before shift
  0000000000000hgf     bit_queue: after shift

Note that for removal, we also decrement bit_count by 5, indicating there are only 3 bits left in the bit_queue.

And finally merge , which takes the bit_queue and temp16 as input (this is on the second iteration):

  0000000000000hgf     bit_queue: remaining bits after shift
  00000000ponmlkji     temp16: next input data byte, zero extended to 16 bits

We need to shift temp16 so that the "i" in temp16 lines up with the "0" next to "h" in bit_queue. The shift amount is simply the bit_count: that's how many bits in the bit_queue we need to keep, so by shifting temp16 by that amount, this will insert bit_count zeros after the data from the input byte.

  temp16 <<= bit_count // bit_count is 3

  00000000ponmlkji     temp16: before shift
  00000ponmlkji000     temp16: after shift

And finally an "or" operation to merge the shifted temp16 into bit_queue:

  0000000000000hgf     bit_queue after removing the first 5 bits
  00000ponmlkji000     temp16: next input data byte, shifted
 ----------------- |   or operation
  00000ponmlkjihgf     new bit_queue after merge operation, 
                        holding 3 bits from input data byte 1,
                         and 8 bits from input data byte 2

Note that the bit_count now goes to 3+8=11.

This bit_queue is now ready to provide another 5-bit code, and so on.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM