• SHL/SHR. Logic shift to the left/right. Fill spaces with 0s. Carry flag equals the last bit moved out from the number(Adjacent to the MSB/LSB).
  • SAL. Arithmetic shift to the left. Equivalent to SHL.
  • SAR. Arithmetic shift to the right. Called arithmetic since preserves the sign. Spaces are filled with the sign of the operand, i.e. the MSB.
  • ROL. Rotate towards the left. The binary numbers rotate on itself towards the left. With this meaning that, if there is only one rotation, the MSB will take the place of the LSB(moved now one to the left). The carry flag holds the value of the last bit moved.
  • ROR. Rotate towards the right. If only one rotation, then the LSB takes the place of the MSB (now moved one to the right). The carry flag holds the last bit moved
  • RCL. Rotate carry left. Like ROL but we need to consider the CF as an additional bit. This is to say that if we have just one rotation on the left, then the MSB goes into CF and the value that CF had goes into the LSB.
  • RCR. Rotate carry right. Like above, it is similar to ROR but the CF is considered as an additional bit.
  • SHLD. Shift left destination . The destination is placed to the left of the source. The former is shifted(logic shift) towards the left and the bits that are freed in its right are filled with bits taken from the source starting from its MSB(left).
  • SHRD. Shift right destination. Destination is placed to the right(D) of the source, a logic shift to the right (SHR) is performed on the destination and the bits that are freed are filled with the ones taken from the source starting from its LSB (right).

Example use SHL in Nasm

In Low Level Programming book we found a nice use for SAR. It has been used for printing the value of rax in hexadecimal. Since we did not find any reason to prefer SAR over SHL for what is accomplished here, we decided to use the latter. The logic is equivalent, because when in the code we use a logical and with a 0000…01111 all the bits before the last nibble(1111) will be cleared.

Recalling that under the hood everything is in binary representation. We present here the main idea of the example. Main idea Suppose we have rax with some value specific value, 0x1122334455667788 for instance, that in binary form is 000100010010….1111. How can we get the leftmost nibble,i.e. the first four digits 0001? We can apply to the 64 bits a SHR of 60 resulting in 0000…0001. It seems enough. It actually is but just for the first 4 bits. What if we wanted to get, for instance, the third leftmost nibble 0010? We could apply as at the previous step a SHR, now of 56. After SHR rax, 56 we would get 00000…00010010010. Here is clear that to isolate the nibble just the shift is not enough. How do we get rid of the unnecessary byte here (0001001)? Bitwise and is the answer. Indeed if we apply a mask like 0xF that has the bits from 4 to 63( recall that notation wise the first bit is called bit 0) all 0s. Therefore the mask we are using for the and operator has a 0000…1111 binary representation.

But after having the nibble, i.e 4 binaries, how do we get is hexadecimal representation, recalling that a nibble is represented as a single digit hex? We need a variable that is the backbone for the translation.

In the section data we create a variable called codes in the following way codes: db '0123456789ABCDEF'. We have now a label. Just as a reminder the concept of variable does not exist in Assembly, what we are doing is labeling some area of the memory. This is important to remember although we will use the word variable ** often. Therefore when we type **codes we are actually using an alias for the memory location (expressed as a 16 hex digits if in 64bit mode) where we find a byte representing the char ‘0’ in Ascii. We can get such a value using indirect addressing through dereferencing with typing byte[codes]. Here the word byte before the dereference operator will make the cpu read from the memory just one byte. Furthermore if we are interested to get, for instance, the char ‘A’ then we need to move from the offset of the variable (the initial address labeled codes) by a specific amount of bytes. Therefore with byte[codes + 10] we move 10 bytes from the offset and read one byte of memory content getting the byte representation of char ‘A’.

To expand more on the example and understand the logic of the algorithm, keep considering rax holding a value of 0x1122334455667788. We have said that hex notation is for the assembler while the cpu knows only binaries. How can we print the first 6 appearing in the hex representation? Such a 6 is in memory is written as a nibble, here bolded 00010001…….010101100110…1111. The answer is: shift + and mask + addressing with displacement([offset+displacement]). Consider the following steps to make rax holding a value of 6

  • shr rax, 20 resulting in 0000….01010110
  • and rax, 0xF resulting in 0000000000110, that in decimal is equal to 6

Once rax has a value of 6 we can displace the offset of codes by 6 and access what is stored in memory with [codes + rax]. In such a memory location we find a byte representation of Ascii ‘6’ and what is missing is just printing it.