Chapter 17
Page 380
-
The register indirect with indexing addressing mode makes it very simple for us to store a value at any location in an array. We need to add only two instructions in the
main
function to solve this problem. They are added right after the call totwiceIndex
.# arrayFifth.s # Allocates an int array, stores 2 X element number # in each element, then stores 123 in fifth element. .intel_syntax noprefix # Stack frame .equ intArray,-48 .equ canary,-8 .equ localSize,-48 # Constant .equ N,10 # Code .text .globl main .type main, @function main: push rbp # save frame pointer mov rbp, rsp # set new frame pointer add rsp, localSize # for local var. mov rax, qword ptr fs:40 # get canary mov qword ptr canary[rbp], rax mov esi, N # number of elements lea rdi, intArray[rbp] # our array call twiceIndex mov rcx, 4 # fifth element mov dword ptr [rdi+rcx*4], 123 # store 123 there mov esi, N # number of elements lea rdi, intArray[rbp] # our array call displayArray mov eax, 0 # return 0; mov rcx, qword ptr canary[rbp] xor rcx, qword ptr fs:40 je allOK call __stack_chk_fail@plt allOK: mov rsp, rbp # restore stack pointer pop rbp # and caller frame pointer ret
-
As you might expect, the compiler does not use the sign extension instruction,
cdqe
, because the index value is unsigned. In thetwiceIndex
function, it replaces the code sequence:mov eax, DWORD PTR -4[rbp] cdqe ## to 64 bits lea rdx, 0[0+rax*4] ## element offset mov rax, QWORD PTR -24[rbp] ## array address add rax, rdx ## element address mov edx, DWORD PTR -4[rbp] ## current i add edx, edx ## 2 x i mov DWORD PTR [rax], edx ## store 2 X i
with:
mov eax, DWORD PTR -4[rbp] ## load i lea edx, [rax+rax] ## 2 x i mov eax, DWORD PTR -4[rbp] ## load i lea rcx, 0[0+rax*4] ## element offset mov rax, QWORD PTR -24[rbp] ## array address add rax, rcx ## element address mov DWORD PTR [rax], edx
Notice that the compiler uses
lea edx, [rax+rax]
to compute two times the index instead of using anadd
instruction. Despite the name, load effective address, thelea
instruction adds the contents of the two registers specified (it’s the same register here,eax
) and stores that sum in the destination register. The values do not need to be used as addresses in the program.
Page 393
-
The effects of this change are easiest to see in the
displayRecord
function. The version in Listing 17-18 copies the twelve bytes that make up the record that is passed to it by value (my comments follow ##):mov QWORD PTR -16[rbp], rdx ## 8 bytes of record mov DWORD PTR -8[rbp], eax ## another 4 bytes movzx eax, BYTE PTR -8[rbp] ## load anotherChar movsx ecx, al ## extend to 32 bits mov edx, DWORD PTR -12[rbp] ## load anInt movzx eax, BYTE PTR -16[rbp] ## load aChar movsx eax, al ## extend to 32 bits
But placing the two
char
elements adjacent to each other reduces the size of the record from twelve to eight bytes:mov QWORD PTR -8[rbp], rdi ## 8 bytes of record movzx eax, BYTE PTR -7[rbp] ## load anotherChar movsx ecx, al ## extend to 32 bits mov edx, DWORD PTR -4[rbp] ## load anInt movzx eax, BYTE PTR -8[rbp] ## load aChar movsx eax, al ## extend to 32 bits
We can verify the placement of the value in the record using
gdb
:(gdb) l displayRecord 4 5 #include <stdio.h> 6 #include "displayRecord.h" 7 8 void displayRecord(struct aTag aRecord) 9 { 10 printf("%c, %i, %c\n", aRecord.aChar, 11 aRecord.anInt, aRecord.anotherChar); 12 } 13 (gdb) b 10 Breakpoint 1 at 0x1240: file displayRecord.c, line 10. (gdb) r Starting program: /home/bob/chapter_17/Your_Turn/recordPassAdj_C/records Breakpoint 1, displayRecord (aRecord=...) at displayRecord.c:10 10 printf("%c, %i, %c\n", aRecord.aChar, (gdb) i r rbp rbp 0x7fffffffde60 0x7fffffffde60 (gdb) x/8xb 0x7fffffffde58 0x7fffffffde58: 0x61 0x62 0x55 0x55 0x7b 0x00 0x00 0x00
Here we see
aChar
at location0x7fffffffde58
,anotherChar
at0x7fffffffde59
, andanInt
at0x7fffffffde5c
(in little endian order). The two bytes at0x7fffffffde5a
and0x7fffffffde5b
are garbage values.(gdb) c Continuing. a, 123, b Breakpoint 1, displayRecord (aRecord=...) at displayRecord.c:10 10 printf("%c, %i, %c\n", aRecord.aChar, (gdb) x/8xb 0x7fffffffde58 0x7fffffffde58: 0x31 0x32 0xff 0xff 0xc8 0x01 0x00 0x00 (gdb) c Continuing. 1, 456, 2 [Inferior 1 (process 3155) exited normally] (gdb)
And when
displayRecord
is called withy
, we see its values stored at the same locations. -
When we pass by pointer,
displayRecord
loads the data from the calling function’s copy of the record rather than make its own copy. As usual, I’ve added my own comments (##) to the compiler’s assembly language to explain things..file "displayRecord.c" .intel_syntax noprefix .text .section .rodata .LC0: .string "%c, %i, %c\n" .text .globl displayRecord .type displayRecord, @function displayRecord: push rbp mov rbp, rsp sub rsp, 16 mov QWORD PTR -8[rbp], rdi ## save address of record mov rax, QWORD PTR -8[rbp] ## load address of record movzx eax, BYTE PTR 8[rax] ## load anotherChar movsx ecx, al ## extend to 32 bits mov rax, QWORD PTR -8[rbp] ## load address of record mov edx, DWORD PTR 4[rax] ## load anInt mov rax, QWORD PTR -8[rbp] ## load address of record movzx eax, BYTE PTR [rax] ## load aChar movsx eax, al ## extend to 32 bits mov esi, eax lea rdi, .LC0[rip] mov eax, 0 call printf@PLT nop leave ret .size displayRecord, .-displayRecord .ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0" .section .note.GNU-stack,"",@progbits