Intel shift instruction traps (transfer) and intel traps
Today, we found a bug in Intel's logical left-shift command shl.
The concept of logical left shift is to move the specified destination operand to the left COUNT times. During each shift, the highest bit is moved into the flag CF, and the lowest Bit is zero. here, OPRD1 is the destination operand, which can be a general-purpose register or a memory operand. First, describe my environment: Intel (R) Pentium (R) 4 CPU, the operating system is Fedora 12, and the gcc version is 4.4.2. See the test procedure below:
#include <stdio.h>
int main()
{
#define MOVE_CONSTANT_BITS 32
unsigned int move_step=MOVE_CONSTANT_BITS;
unsigned int value1 = 1ul << MOVE_CONSTANT_BITS;
printf("value1 is 0x%X\n", value1);
unsigned int value2 = 1ul << move_step;
printf("value2 is 0x%X\n", value2);
return 0;
}
Compile: [root @ Lnx99 test] # gcc-g test. c-o test
Test. c: In function 'main ':
Test. c: 8: warning: left shift count> = width of type. Here, I want to ask you, what are the values of these two values? Are they equal? I believe a lot of people will say that these two values are the same, and they are all 0. because according to the concept of logical left shift, this 1 is removed, and 32 zeros are supplemented at the low level. so the value must be zero. Let me execute it. [Root @ Lnx99 test] #./test
Value1 is 0x0
Value2 is 0x1
It's strange. Why. Let's take a look at the assembly code.
- Dump of worker er code for function main:
- 0x080483c4 <main + 0>: push % ebp
- 0x080483c5 <main + 1>: mov % esp, % ebp
- 0x080483c7 <main + 3>: and $0xfffffff0, % esp
- 0x080483ca <main + 6>: push % ebx
- 0x080483cb <main + 7>: sub $ 0x2c, % esp
- 0x080483ce <main + 10>: movl $0x20, 0x14 (% esp)
- 0x080483d6 <main + 18>: movl $0x0, 0x18 (% esp)
- 0x080483de <main + 26>: mov $0x80484f4, % eax
- 0x080483e3 <main + 31>: mov 0x18 (% esp), % edx
- 0x080483e7 <main + 35>: mov % edx, 0x4 (% esp)
- 0x080483eb <main + 39>: mov % eax, (% esp)
- 0x080483ee <main + 42>: call 0x80482f4 <printf @ plt>
- 0x080483f3 <main + 47>: mov 0x14 (% esp), % eax
- 0x080483f7 <main + 51>: mov $0x1, % edx
- 0x080483fc <main + 56>: mov % edx, % ebx
- 0x080483fe <main + 58>: mov % eax, % ecx
- 0x08048400 <main + 60>: shl % cl, % ebx
- 0x08048402 <main + 62>: mov % ebx, % eax
- 0x08048404 <main + 64>: mov % eax, 0x1c (% esp)
- 0x08048408 <main + 68>: mov $0x8048504, % eax
- 0x0804840d <main + 73>: mov 0x1c (% esp), % edx
- 0x08048411 <main + 77>: mov % edx, 0x4 (% esp)
- 0x08048415 <main + 81>: mov % eax, (% esp)
- 0x08048418 <main + 84>: call 0x80482f4 <printf @ plt>
- 0x0804841d <main + 89>: mov $0x0, % eax
- 0x08048422 <main + 94>: add $ 0x2c, % esp
- 0x08048425 <main + 97>: pop % ebx
- 0x08048426 <main + 98>: mov % ebp, % esp
- 0x08048428 <main + 100>: pop % ebp
- 0x08048429 <main + 101>: ret
- End of worker er dump.
The red code in the Assembly Code corresponds to unsigned int value1 = 1ul <MOVE_CONSTANT_BITS; the Blue Code corresponds to unsigned int value2 = 1ul <move_step; as can be seen from these codes, for the first command, gcc directly calculates the result value and assigns it to value1, while the second Command actually executes the logic shift shl left. But why is the result of the logical left-shift shl operation 1 instead of 0. The result of the logical left shift is the same as the result of the loop left shift ROL. At this point, I am a bit skeptical about whether it is a compiler problem. When generating machine code, is it wrong to generate the machine code corresponding to ROL. Use objdump-d test to view the machine code of test. The machine code corresponding to the logical left shift is d3 e3.8048400: d3 e3 shl % cl, and % ebx can only be modified by means of assembly code to shift left ROL by repeating the loop. First, use gcc-S test. c. Generate the assembly code test. s, then modify sall % cl, % ebx behavior roll % cl, % ebx, and then use gcc-g test. s-o test assembly code test. s to regenerate test. Use objdump-d test again to view the machine code of test. The machine code corresponding to the cycle shifts left is d3 c3. 8048400: d3 c3 rol % cl, % ebx so far, we can determine that the compiler is correct and that the logic left-shift instruction provided by Intel is used. Why is the final result different from the Expected One. Is it an Intel bug ?! We cannot draw this conclusion easily. Because the Left shift of logic is a very basic command, Will Intel have such an obvious bug? Let's take a look at Intel's instruction manual. SAL/SAR/SHL/SHR-Shift (Continued) -- 32-bit Server
Description
These instructions shift the bits in the first operand (destination operand) to the left or right
The number of bits specified in the second operand (count operand). Bits shifted beyond
Destination operand boundary are first shifted into the CF flag, then discarded. At the end of
Shift operation, the CF flag contains the last bit shifted out of the destination operand.
The destination operand can be a register or a memory location. The count operand can be
Immediate value or register CL. The count is masked to five bits, which limits the count range
To 0 to 31. A special opcode encoding is provided for a count of 1. Originally, on 32-bit machines, the shift counter had only five digits. When 32 bits are left shifted, 0 bits are actually left shifted. Then the 1ul <move_step is equivalent to 1ul <0. Then value2 is actually 1. At this point, although we already know the ins and outs of our children, we can't say that Intel's shift command has a trap. This is because this situation has not been mentioned in other assembler languages except in Intel's manual. Some may have said that gcc has already given a "test. A warning like c: 8: warning: left shift count> = width of type has been reported. With regard to this warning, if the code is more complex, the number of shifts is no longer a constant, and gcc cannot detect it. Therefore, when we need to do shift processing, we must pay attention to whether it exceeds 32 bits (64 bits ). In addition, I have some comments on gcc processing. When 1ul <32, the pre-processing result of gcc itself does not match the result of the operation, although it is more in line with user expectations. However, when the user starts to use constants, the result is correct. Once changed to a variable, the result is different. In large programs, this will make it difficult for users to locate the problem.
Note: The default type of a constant is int. To ensure portability, the type of a constant must be displayed when constant calculation may result in an out-of-bounds operation. For example:Printf ("1ul <40 = % llx \ n", 1ul <40 );