Stanfrancisco
Stanfrancisco

Reputation: 352

What is happening in this disassembled code, and what would it look like in C?

I've disassembled this c code (using ida), and ran across this bit of code. I believe the second line is an array, as well as the 5th line, but I'm not sure why it uses a sign extend or a zero extend.

I need to convert the code to C, and I'm not sure why the sign/zero extend is used, or what C code would cause that.

mov     ecx, [ebp+var_58]
mov     dl, byte ptr [ebp+ecx*2+var_28]
mov     [ebp+var_59], dl
mov     eax, [ebp+var_58]
movsx   ecx, [ebp+eax*2+var_20]
movzx   edx, [ebp+var_59]
or      edx, ecx
mov     [ebp+var_59], dl

Upvotes: 0

Views: 270

Answers (1)

Peter Cordes
Peter Cordes

Reputation: 363970

unsigned integer types will be zero-extended, while signed types will be sign-extended.


I kinda want to downvote this as too trivial. It's not like there's anything going on that the instruction reference manual doesn't cover. I guess it's different from asking for an explanation of a really simple C program because the trick here is understanding why one might string this sequence of instructions together, rather than just what each one does individually. Being familiar with the idioms used by non-optimizing compilers (store and reload from RAM after every statement) helps.


I'm guessing this is a snippet from inside a function that makes a stack frame, so positive offsets from ebp are where local variables are spilled when they're not live in registers.

mov     ecx, [ebp+var_58]     ; load var58 into ecx
mov     dl, byte ptr [ebp+ecx*2+var_28]   ; load a byte from var28[2*var58]
mov     [ebp+var_59], dl      ; store it to var59
mov     eax, [ebp+var_58]     ; load var58 again for some reason?  can var59 alias var58?
;  otherwise we still have the value in ecx, right?
;  Or is this non-optimizing compiler output that's really annoying to read?
movsx   ecx, [ebp+eax*2+var_20]   ; load var20[var58*2]
movzx   edx, [ebp+var_59]         ; load var59 again
or      edx, ecx                  ; edx = var59|var20[var58*2]
mov     [ebp+var_59], dl          ; spill var59 back to memory

I guess the default operand size for movsx/movzx is byte-to-dword. word-to-dword also exists, and I'm surprised your disassembler didn't disambiguate with a byte ptr on the memory operand. I'm inferring that it's a byte load because the preceding store to that address was byte-wide.

movsx is used when loading signed data that's smaller than 32b. C's integer-promotion rules dictate that operations on integer types smaller than int are automatically promoted to int (or unsigned int if int can't represent all values. e.g. if unsigned short and unsigned int are the same size).

8bit or 32bit operand sizes are available without operand-size prefix bytes. Some only Intel P6/SnB family CPUs track partial-register dependencies, sign-extending to a full register width on loads can make for faster code (avoiding false dependencies on the previous contents of the register on AMD and Silvermont). So sign or zero extending (as appropriate for the data type) on loads is often the best way to handle narrow memory locations.


Looking at the output of non-optimizing compilers is not usually worth bothering with.

If the code had been generated by a proper optimizing compiler, it would probably be more like

mov     ecx, [ebp+var_58]     ; var58 is live in ecx
mov     al, byte ptr [ebp+ecx*2+var_28]   ; var59 = var28[2*var58]
or      al, [ebp+ecx*2+var_20]    ; var59 |= var20[var58*2]
mov     [ebp+var_59], al          ; spill var59 to memory

Much easier to read, IMO, without the noise of constantly storing/reloading. You can see when a value is used multiple times without having to notice that a load was from an address that was just stored to.

If a false dependency on the upper 24 bits of eax was causing a problem, we could use movzx or movsx loads into two registers, and do an or r32, r32 like the original, but then still store the low 8. (Using a 32bit or with a memory operand would do a 4B load, not a 1B load, which could cross a cache line or even a page and segfault.)

Upvotes: 2

Related Questions