Chapter 5 Exercises: Your Development Environment

Section A: Toolchain Setup and Verification

Exercise 5.1 — Install and Verify

Install the required tools and verify each:

nasm --version
ld --version
gdb --version
objdump --version
readelf --version
nm --version
strace --version

For each tool, record the version number. If any tool is missing, install it. Paste your version output here (annotate a text file, not in code).


Exercise 5.2 — Hello World Workflow

Follow the complete workflow for the hello.asm program from the chapter:

a) Save hello.asm to a file b) Assemble: nasm -f elf64 hello.asm -o hello.o c) Link: ld hello.o -o hello d) Run: ./hello — verify you see "Hello, Assembly!" e) Check exit code: echo $? — verify it's 0 f) Run with strace: strace ./hello — copy the strace output and identify each system call

For step (f), annotate each line of the strace output explaining what it does.


Exercise 5.3 — Binary Analysis

For the hello binary you built in Exercise 5.2:

a) Run readelf -h hello — what is the entry point address? What is the machine type (e_machine field)?

b) Run readelf -S hello — list all sections and their sizes. Which section contains the code? Which contains the string "Hello, Assembly!"?

c) Run objdump -d -M intel hello — paste the full disassembly. Annotate each instruction with what it does.

d) Run nm hello — what symbols are defined? What is the address of _start? Is msg a symbol? (It shouldn't be — it's a local label without global.)

e) Run objdump -h hello — find the .text section. What are its: VMA (virtual address), file offset, and size?


Exercise 5.4 — Object File vs Executable

Compare the object file and executable:

a) Run objdump -d hello.o — notice the addresses. What address does _start have in the object file? Is this different from the executable?

b) Run readelf -r hello.o — do you see any relocation entries? What do they represent?

c) The msg reference in the mov rsi, msg instruction appears as 0x00000000 in hello.o but as a real address in hello. Where does the real address come from?


Section B: GDB Mastery

Exercise 5.5 — Basic GDB Walkthrough

Start GDB on hello and perform the following sequence:

gdb hello
(gdb) break _start
(gdb) run
(gdb) info registers        ; write down all register values
(gdb) stepi                 ; after each step, predict the next register change
(gdb) info registers        ; verify your prediction
; (repeat until the program exits)

For each stepi, write down: - Which instruction was just executed - Which registers changed - What the new values are


Exercise 5.6 — Memory Examination

After breaking at _start and before any steps:

a) Use x/s to print the string at the address that will go into RSI. (Hint: the address is visible in the disassembly of mov rsi, msg.)

b) Use x/17xb to show the 17 bytes of the message as individual hex bytes. Verify that each byte matches the expected ASCII value.

c) Use x/8gx $rsp to show the top 8 quadwords of the stack. What is at [rsp]? (Hint: it should be the argument count for the program.)

d) Use x/i $rip to show the instruction at the current program counter. Does it match what you expect?


Exercise 5.7 — Auto-Display and TUI

Set up auto-display for the five key registers and use TUI mode:

(gdb) display /x $rax
(gdb) display /x $rdi
(gdb) display /x $rsi
(gdb) display /x $rdx
(gdb) display /x $rflags
(gdb) layout asm    ; switch to TUI assembly view
(gdb) layout regs   ; add register window

Step through the entire program. For each step: a) Record which instruction executes b) Record which auto-display values change and to what c) After syscall (sys_write), what does RAX contain? Why?


Exercise 5.8 — Setting Breakpoints on Addresses

For a slightly more complex program, write the following:

; count.asm — count down from 5 to 0
section .data
    msg     db "Counting: "
    msg_len equ $ - msg

section .bss
    digit   resb 2      ; "N\n"

section .text
    global _start

_start:
    mov  rcx, 5         ; counter

.loop:
    ; Print "Counting: "
    mov  rax, 1
    mov  rdi, 1
    lea  rsi, [rel msg]
    mov  rdx, msg_len
    syscall

    ; Convert counter to ASCII and print
    mov  rax, rcx
    add  al, '0'        ; convert to ASCII digit
    lea  rsi, [rel digit]
    mov  [rsi], al
    mov  BYTE [rsi+1], 10    ; newline
    mov  rax, 1
    mov  rdi, 1
    mov  rdx, 2
    syscall

    dec  rcx
    jnz  .loop          ; if rcx != 0, loop

    ; Exit
    mov  rax, 60
    xor  rdi, rdi
    syscall

a) Assemble and run it. What is the output? b) In GDB, set a breakpoint on the .loop label. How many times does GDB stop there? c) Set a conditional breakpoint that only stops when RCX equals 3: break .loop if $rcx == 3. Verify it stops exactly once. d) Use continue (not stepi) to resume execution between breakpoints.


Section C: Build Automation

Exercise 5.9 — Makefile for Multiple Files

Create a Makefile for a project with three assembly files:

project/
├── Makefile
├── main.asm        ; contains _start, calls functions in util.asm
├── util.asm        ; contains print_string, print_newline
└── math.asm        ; contains add_two_numbers

The Makefile should: - Build all three .asm files into .o files - Link them together: ld main.o util.o math.o -o project - Have a make run target - Have a make clean target - Have a make disasm target that shows all three object files' disassemblies

Write the Makefile (you don't need to implement the .asm files — just the Makefile structure).


Exercise 5.10 — Debug vs. Release Builds

Modify the Makefile to support two build modes:

# Debug build (with symbols, no optimization context)
debug: NASM_FLAGS = -f elf64 -g -F dwarf
debug: $(TARGETS)

# Release build (no debug symbols, stripped)
release: NASM_FLAGS = -f elf64
release: $(TARGETS)
release:
    strip $(TARGETS)

a) Build in debug mode and check: readelf -S hello | grep debug — are there .debug_* sections? b) Build in release mode and strip. What is the size difference of the binary? c) Try to set a source-line breakpoint in GDB on the debug vs. release binary. What's different?


Section D: C and Assembly Interop

Exercise 5.11 — Assembly Called from C

Write a function in assembly that C code can call:

; add_ints.asm
; long add_ints(long a, long b)
; Returns a + b
; System V ABI: a in rdi, b in rsi, return in rax

section .text
    global add_ints

add_ints:
    ; Your implementation here (2 lines)
    ret

And the C test:

// test_add.c
#include <stdio.h>

extern long add_ints(long a, long b);

int main(void) {
    printf("%ld\n", add_ints(3, 4));    // should print 7
    printf("%ld\n", add_ints(-1, 1));   // should print 0
    printf("%ld\n", add_ints(0x7FFFFFFFFFFFFFFF, 1));  // overflow!
    return 0;
}

Build:

nasm -f elf64 add_ints.asm -o add_ints.o
gcc test_add.c add_ints.o -o test_add
./test_add

What does the overflow case print? Explain why.


Exercise 5.12 — C Calling Assembly Calling C

Write a program where: - C calls an assembly function array_sum - array_sum sums elements of a long[] array - It calls no C functions

; array_sum.asm
; long array_sum(long *arr, size_t n)
; Returns sum of arr[0..n-1]

section .text
    global array_sum

array_sum:
    ; Your implementation (use a loop with indirect addressing)
    ; Template:
    xor  eax, eax       ; rax = 0 (sum)
    test rsi, rsi       ; n == 0?
    jz   .done
.loop:
    add  rax, QWORD [rdi]   ; sum += *arr
    add  rdi, 8             ; arr++
    dec  rsi                ; n--
    jnz  .loop
.done:
    ret

Test with:

long arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("%ld\n", array_sum(arr, 10));   // expected: 55

Section E: Toolchain Deep Dives

Exercise 5.13 — objdump Flag Exploration

Run each of the following objdump commands on hello and explain what the output shows:

objdump -d -M intel hello          # (a) Intel syntax disassembly
objdump -d -M att hello            # (b) AT&T syntax disassembly (default)
objdump -t hello                   # (c) symbol table
objdump -h hello                   # (d) section headers
objdump -p hello                   # (e) program headers (ELF)
objdump -R hello                   # (f) dynamic relocations (if applicable)
objdump --disassemble=_start hello  # (g) disassemble only _start

For (g), your version of objdump may not support --disassemble=symbol. If not, use objdump -d hello | head -30 to get the first 30 lines.


Exercise 5.14 — readelf vs objdump

Both readelf and objdump can show section information. Compare:

readelf -S hello    # section headers
objdump -h hello    # section headers

a) What information does readelf -S show that objdump -h doesn't? (Hint: look at the "Flags" column in readelf's output and what "SHF_ALLOC", "SHF_EXECINSTR", "SHF_WRITE" mean.)

b) What is the difference between a section and a segment? Use readelf -l hello to show segments (program headers) and explain how sections map to segments.


Exercise 5.15 — The QEMU Hello World

This exercise runs your hello.asm program inside a QEMU system emulator to verify your QEMU installation:

# Method 1: use QEMU's user-mode emulation (if available)
# This runs an x86-64 binary on any host architecture
qemu-x86_64 ./hello

# Method 2: create a minimal bootable image (teaser for Chapter 7)
# We won't do a full bootloader yet, but verify QEMU works:
qemu-system-x86_64 --version
qemu-system-x86_64 -nographic -no-reboot -m 8M /dev/null 2>&1 | head -5
# Should print QEMU version info and possibly a "No bootable device" message

If QEMU user-mode (qemu-x86_64) is available, run your assembly program under it. Note any differences in behavior or output.