|
| 1 | +# C <-> ASM Interface Examples (x86-64, System V AMD64) |
| 2 | + |
| 3 | +This folder contains 3 examples that mirror the style of `func-cdecl`, focused on calling between C and assembly. |
| 4 | + |
| 5 | +## Files |
| 6 | + |
| 7 | +- `asm_call_c_main.asm` + `asm_call_c_sum.c` |
| 8 | + - Assembly `main` defines the array in `.data`, calls a C function that sums the array, then prints the result. |
| 9 | + |
| 10 | +- `c_call_asm_main.c` + `c_call_asm_sum.asm` |
| 11 | + - C `main` has a globally initialized array and calls an assembly function to compute the sum. |
| 12 | + |
| 13 | +- `inline_asm.c` |
| 14 | + - Single C file that uses GCC inline assembly to sum an array. |
| 15 | + |
| 16 | +## Build and run |
| 17 | + |
| 18 | +```bash |
| 19 | +make |
| 20 | +./asm_call_c |
| 21 | +./c_call_asm |
| 22 | +./inline_asm |
| 23 | +``` |
| 24 | + |
| 25 | +## Clean |
| 26 | + |
| 27 | +```bash |
| 28 | +make clean |
| 29 | +``` |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## GCC Extended Inline Assembly |
| 34 | + |
| 35 | +### Basic structure |
| 36 | + |
| 37 | +```c |
| 38 | +__asm__ volatile ( |
| 39 | + "asm instructions" |
| 40 | + : outputs |
| 41 | + : inputs |
| 42 | + : clobbers |
| 43 | +); |
| 44 | +``` |
| 45 | + |
| 46 | +Each section is separated by `:`. Any section can be empty (just leave it blank or omit trailing colons). |
| 47 | + |
| 48 | +--- |
| 49 | + |
| 50 | +### Why `volatile`? |
| 51 | + |
| 52 | +Without `volatile`, GCC treats the asm block like a pure function: if it thinks the outputs are unused or the block can be hoisted/eliminated/reordered, it will do so. |
| 53 | + |
| 54 | +`volatile` suppresses all of that: |
| 55 | +- The block is **always emitted**, even if the output is unused. |
| 56 | +- The block is **not moved** relative to other memory operations. |
| 57 | +- Multiple identical blocks are **not merged** into one. |
| 58 | + |
| 59 | +Use `volatile` whenever the asm has side effects beyond its declared outputs (e.g. it reads/writes memory, modifies flags, or does I/O). Omit it only for pure computational blocks where GCC optimizing away redundant calls is acceptable. |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +### Outputs |
| 64 | + |
| 65 | +```c |
| 66 | +: "=&r" (sum) |
| 67 | +``` |
| 68 | + |
| 69 | +Each output is `"constraint" (c_variable)`. The variable receives the register value after the block. |
| 70 | + |
| 71 | +| Modifier | Meaning | |
| 72 | +|---|---| |
| 73 | +| `=` | write-only - value on entry is discarded | |
| 74 | +| `+` | read-write - value is read on entry and written on exit | |
| 75 | +| `&` | early-clobber - this register is written before all inputs are consumed; GCC must not share it with any input | |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +### Inputs |
| 80 | + |
| 81 | +```c |
| 82 | +: "r" (arr), "r" (n) |
| 83 | +``` |
| 84 | + |
| 85 | +Each input is `"constraint" (c_expression)`. GCC loads the value into the chosen location before the block. Inputs are numbered after outputs: if there is one output (`%0`), inputs start at `%1`, `%2`, ... |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +### Operand constraints |
| 90 | + |
| 91 | +| Constraint | Location | |
| 92 | +|---|---| |
| 93 | +| `r` | any general-purpose register (GCC chooses) | |
| 94 | +| `a` | `rax` / `eax` | |
| 95 | +| `b` | `rbx` / `ebx` | |
| 96 | +| `c` | `rcx` / `ecx` | |
| 97 | +| `d` | `rdx` / `edx` | |
| 98 | +| `S` | `rsi` / `esi` | |
| 99 | +| `D` | `rdi` / `edi` | |
| 100 | +| `m` | memory operand | |
| 101 | +| `i` | immediate integer constant | |
| 102 | + |
| 103 | +With `r`, GCC picks the register - use size modifiers in the asm string to get the right-width name: |
| 104 | + |
| 105 | +| Modifier | Register size | Example (`%0` → `rsi`) | |
| 106 | +|---|---|---| |
| 107 | +| `%q0` | 64-bit | `rsi` | |
| 108 | +| `%k0` | 32-bit | `esi` | |
| 109 | +| `%w0` | 16-bit | `si` | |
| 110 | +| `%b0` | 8-bit | `sil` | |
| 111 | + |
| 112 | +With specific constraints (`a`, `c`, `S`, ...) you know the register name and can write it directly. |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +### Named operands |
| 117 | + |
| 118 | +Instead of positional `%0`, `%1`, you can use names: |
| 119 | + |
| 120 | +```c |
| 121 | +__asm__ volatile ( |
| 122 | + "add %k[sum], dword ptr [%q[arr] + r8 * 4]" |
| 123 | + : [sum] "=&r" (sum) |
| 124 | + : [arr] "r" (arr), [n] "r" (n) |
| 125 | + : "r8", "cc", "memory" |
| 126 | +); |
| 127 | +``` |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +### Clobbers |
| 132 | + |
| 133 | +Declare every register and resource the asm modifies that is not an output: |
| 134 | + |
| 135 | +| Clobber | Meaning | |
| 136 | +|---|---| |
| 137 | +| `"rax"`, `"r8"`, ... | asm modifies this register | |
| 138 | +| `"cc"` | asm modifies RFLAGS (`add`, `sub`, `cmp`, `test`, `inc`, `dec`, ...) | |
| 139 | +| `"memory"` | asm reads/writes memory GCC cannot see; acts as a compiler barrier - GCC flushes cached values and cannot reorder memory ops across the block | |
| 140 | + |
| 141 | +Omitting a clobber is undefined behaviour: GCC may place a live value in that register and your asm will silently corrupt it. |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +### Numeric labels |
| 146 | + |
| 147 | +Use numbers instead of named labels to avoid collisions when the same asm block is inlined multiple times: |
| 148 | + |
| 149 | +```c |
| 150 | +"test ecx, ecx\n\t" |
| 151 | +"jle 2f\n\t" // jump forward to label 2 |
| 152 | +"1:\n\t" // loop top |
| 153 | +" ...\n\t" |
| 154 | +"jl 1b\n\t" // jump backward to label 1 |
| 155 | +"2:\n\t" // exit |
| 156 | +``` |
| 157 | + |
| 158 | +`f` = forward, `b` = backward. The same number can appear many times; the direction disambiguates which instance is the target. |
0 commit comments