Assignment

We’ll begin with simple assignment of integer types. In particular, we consider:

  ia = 3;
  uia = 3;
  ca = 3;
  uca = 3;
  la = 3;
  ula = 3;

  ib = 4;
  uib = 4;
  cb = 4;
  ucb = 4;
  lb = 4;
  ulb = 4;

AMD64

The equivalent assembly is shown in the following table:

C .s file gdb
ia = 3;
movl $3, -8(%rbp) movl $0x3,-0x8(%rbp)
uia = 3;
movl $3, -20(%rbp) movl $0x3,-0x14(%rbp)
ca = 3;
movb $3, -29(%rbp) movb $0x3,-0x1d(%rbp)
uca = 3;
movb $3, -32(%rbp) movb $0x3,-0x20(%rbp)
la = 3;
movq $3, -48(%rbp) movq $0x3,-0x30(%rbp)
ula = 3;
movq $3, -72(%rbp) movq $0x3,-0x48(%rbp)
ib = 4;
movl $4, -12(%rbp) movl $0x4,-0xc(%rbp)
uib = 4;
movl $4, -24(%rbp) movl $0x4,-0x18(%rbp)
cb = 4;
movb $4, -30(%rbp) movb $0x4,-0x1e(%rbp)
ucb = 4;
movb $4, -33(%rbp) movb $0x4,-0x21(%rbp)
lb = 4;
movq $4, -56(%rbp) movq $0x4,-0x38(%rbp)
ulb = 4;
movq $4, -80(%rbp) movq $0x4,-0x50(%rbp)

Here we see that an integer assignment is a simple movl (for 4-byte values), movb (for 1-byte values), or movq (for 8-byte values), regardless of whether the variable is signed or unsigned. The literal value is preceded by a $, and registers are preceded by %. The syntax -8(%rbp) means offset by -8 bytes from the value of the register rbp. The literals and offsets are written (by default) in decimal in the assembly file, but hexadecimal in gdb. Otherwise, there is no difference.

AArch64

The equivalent assembly is shown in the following table:

C .s file gdb
ia = 3;
mov r3, #3 mov r3, #3
str r3, [fp, #-8] str r3, [r11, #-8]
uia = 3;
mov r3, #3 mov r3, #3
str r3, [fp, #-12] str r3, [r11, #-12]
ca = 3;
mov r3, #3 mov r3, #3
strb r3, [fp, #-13] strb r3, [r11, #-13]
uca = 3;
mov r3, #3 mov r3, #3
strb r3, [fp, #-14] strb r3, [r11, #-14]
la = 3;
mov r3, #3 mov r3, #3
str r3, [fp, #-20] str r3, [r11, #-20]
ula = 3;
mov r3, #3 mov r3, #3
str r3, [fp, #-24] str r3, [r11, #-24]
ib = 4;
mov r3, #4 mov r3, #4
str r3, [fp, #-28] str r3, [r11, #-28]
uib = 4;
mov r3, #4 mov r3, #4
str r3, [fp, #-32] str r3, [r11, #-32]
cb = 4;
mov r3, #4 mov r3, #4
strb r3, [fp, #-33] strb r3, [r11, #-33]
ucb = 4;
mov r3, #4 mov r3, #4
strb r3, [fp, #-34] strb r3, [r11, #-34]
lb = 4;
mov r3, #4 mov r3, #4
str r3, [fp, #-40] str r3, [r11, #-40]
ulb = 4;
mov r3, #4 mov r3, #4
str r3, [fp, #-44] str r3, [r11, #-44]

The instructions on the ARM platform are more complex. Where before we had a direct write to the variables’ stack locations, we now begin with a mov instruction to write the value to register r3. We then store this register on the stack using str or strb (for 1-byte values). The frame pointer is given by register fp, and we use the syntax [fp, #-8] to indicate an offset of -8 bytes from the value of the register fp.

In gdb, we see the same pattern of mov and str (or strb), again with arguments in the same order. The register fp from the .s file is now shown as r11 instead, though they refer to the same thing. gdb will recognize fp as the name of a register, but it is identical to r11. You can try this yourself with the command info reg fp r11.