Four tools
The is four programs, each producing one kind of file:
From hello.c to words in memory, and back to text: four tools
Each box is one program run by the compile tool. Each arrow carries one kind of file.
What to notice
Only the last box on the top row is a translation back. The ELF in the middle is the real product. The compile tool splits it into the two memory images the core loads. Everything to its left is the software side of §1.1’s contract line.
gcc -S hello.cruns the compiler: C in, assembly text out. The text uses register numbers ($2) or names and local labels (.L3).asis the : text in, words out. It fills branch offsets from labels and expands the pseudo-instructions below.ldis the . It joinscrt0andhello.o, and places them according to the . The result is an file. In it, everyjaland everylui/addiupair holds its final address.objdump -dprints the : the dump files this book has quoted since §1.2.
Your flags, one line each
The compile tool passes these to gcc for the MIPS target:
| flag | what it does |
|---|---|
-march=mips32r2 | allow release-2 instructions (seb, mul, ext, …) |
-mabi=32 | the o32 convention: $a0–$a3, 8-byte frames, the argument area (§2.2, §4.4) |
-msoft-float | no floating-point instructions: your core has none |
-mno-abicalls -fno-pic | plain absolute code, no position-independent tables |
-G0 | nothing in the small-data area, so $gp is set but unused |
-mno-imadd | no madd/msub: four sources, outside your core’s set (§7.2) |
-mno-check-zero-division | no teq after divides: teq is outside the set (§7.2) |
-fno-delayed-branch | every delay slot is a nop: your core squashes the slot (§5.4) |
-mno-branch-likely | no beql/bnel: the likely branches are outside the set |
Each flag closes a gap between what GCC could emit and what your core implements. The compile tool then verifies every word against the description. A flag that slipped would be caught before the core ran.
Pseudo-instructions: spellings, not instructions
The assembler accepts spellings that are not in the instruction set and rewrites them. You have met most of them already. Here they are in one table. Each row ends with a word from a real dump, or from the site’s own assembler:
Pseudo-instructions and what the assembler really emits
hello_o0.dump, fib_o0.dump, and the site's assembler for the rest
| move rd,rs | or rd,rs,$zero | 00000825 = move at,zero | hello_o0.dump bfc00000 (crt0) |
| li rt,10 | addiu rt,$zero,10 (fits 16 signed bits) | 2404000a = li a0,10 | hello_o0.dump bfc001cc |
| li rt,0xffff | ori rt,$zero,0xffff (fits 16 unsigned) | 3409ffff = li t1,0xffff | the site's assembler |
| li rt,0x10003ff0 | lui rt,0x1000 + ori rt,rt,0x3ff0 | 3c021000 + 34423ff0 | hello_o0.dump bfc000c8, bfc000cc |
| la rt,_gp | lui rt,%hi(_gp) + addiu rt,rt,%lo(_gp) | 3c1c1001 + 279c8010 | hello_o0.dump bfc0007c, bfc00080 |
| b label | beq $zero,$zero,label | 1000000c = b bfc001e4 | hello_o0.dump bfc001b0 |
| bal label | bgezal $zero,label | 04110000 | the site's assembler |
| beqz rs,label | beq rs,$zero,label | 10400004 = beqz v0,bfc002a4 | fib_o0.dump bfc00290 |
| bnez rs,label | bne rs,$zero,label | 1440fff2 = bnez v0,bfc001b8 | hello_o0.dump bfc001ec |
| nop | sll $zero,$zero,0 | 00000000 | every delay slot in every dump |
| negu rd,rs | subu rd,$zero,rs | 00094823 = negu t1,t1 | the site's assembler |
| not rd,rs | nor rd,rs,$zero | 01204027 = not t0,t1 | the site's assembler |
What to notice
Every entry in the third column is an ordinary word from the 63. move is an or with $zero. nop is a shift of $zero. b is a beq of $zero with itself. objdump prints the alias back when it recognises the pattern. That is why the dumps look as if these instructions existed.
Some expansions need a scratch register. A two-register comparison such as blt is one example. That is why $1 is $at, the assembler temporary, and why compilers never keep a value there. crt0_mips.S says .set noat because it uses none of those forms. It says .set noreorder because it writes its own delay slots (§5.4).
Reading a dump line
Here is the loop of hello.c three ways. The C, the -O0 assembly as objdump prints it, and the raw words. Click a C line.
One loop, three views: C, disassembly, words (hello_o0.dump bfc001ac–bfc001f0)
carolyne_ref/programs/hello.c; carolyne_ref/dumps/hello_o0.dump
1for (i = 0;2 i < 5;3 i++) {4 print_int(i * i);5 print_char('\n');6}bfc001acsw zero,16(s8)bfc001b0b bfc001e4 <main+0x58>bfc001b4nopbfc001b8lw v0,16(s8)bfc001bcmul v0,v0,v0bfc001c0move a0,v0bfc001c4jal bfc000ec <print_int>bfc001c8nopbfc001ccli a0,10bfc001d0jal bfc000b0 <print_char>bfc001d4nopbfc001d8lw v0,16(s8)bfc001dcaddiu v0,v0,1bfc001e0sw v0,16(s8)bfc001e4lw v0,16(s8)bfc001e8slti v0,v0,5bfc001ecbnez v0,bfc001b8 <main+0x2c>bfc001f0nop1afc0001021000000c30000000048fc2001057042100260040202570ff0003b80000000092404000a100ff0002c1100000000128fc20010132442000114afc20010158fc200101628420005171440fff21800000000Click any highlighted line to see it light up in the other listing, and read what it does.
What to notice
Three things in the middle pane were computed by objdump, not stored in the word. The branch targets. The <symbol+offset> annotations. The aliases b, move, li, bnez. The right pane is what the core actually
reads. Every dump line is address: word mnemonic operands, and only the
first two columns are facts about memory.
Before reading on, decide:
A dump line reads `bfc00090: 00000000 nop`. Which parts of it are stored in memory, and which did objdump add?
Go deeper: what the compile tool adds, and why it verifies every word
Carolyne’s examples/compile_tool runs the four steps for either target
from one description of the machine’s memories. It generates the linker script and carolyne_io.h from one MemoryLayout. So the addresses in the header, the script and the hardware cannot disagree. After linking it does one thing GCC never does. It decodes every executable word with the real decoder functions of the ISA description. A word that matches zero instructions, or two, is refused. For MIPS it also refuses any delay slot
that is not nop.
That check is why the flags above are not just preferences. -mno-imadd without the verifier would let a madd slip through and fail silently on the core. With it, the build stops and names the address. The census over 34 builds, quoted often in this book, ran exactly that decoder over every word and counted mnemonics.
Can you now explain it?
gcc -S writes text, as makes words and expands pseudo-instructions, ld places the words by the linker script into an ELF, objdump -d prints them back with aliases and computed targets. Only the words are real.
If you remember only three things
- Four tools, four files. hello.c → .s → .o → .elf → .dump. The ELF is the product; the dump is a reading of it.
- Pseudo-instructions are spellings. move is or, li is addiu or lui + ori, b is beq $zero,$zero, nop is sll $zero. $at exists so the assembler has a scratch register.
- A dump line has two facts and one opinion. The address and the word are memory; the mnemonic, the target and the <symbol> are objdump's.
Now try to explain this one yourself: the compile tool builds with
-fno-delayed-branch and then checks every slot is nop. If someone dropped
the flag but kept the check, what would happen, and at which tool?
Go deeper: a model answer, once you have tried
GCC would fill many slots with useful instructions, as it does by default. A move or an addiu would move under a branch. The assembler and linker would
accept them, since the words are legal. The compile tool’s verifier runs after the link. It would find the first slot that is not 0x00000000, stop the build, and name the branch’s address. Nothing would reach the core. That is the point of checking the words rather than trusting the flags. The guard is at the last step, where every earlier mistake is visible.
Check your understanding
0/5 answered1.What does the assembler emit for `li $t0,0x12345678`?
2.Why is register $1 called $at and kept free by compilers?
3.Which compiler flag makes every delay slot in your dumps a nop?
4.The dump shows `00000825 move at,zero`. What instruction is the word 0x00000825 really?
5.A dump line ends with `bnez v0,bfc001b8 <main+0x2c>`. What does `<main+0x2c>` mean?
Next: §9.2 — booting: the reset vector, crt0 and the memory map.