diff --git a/curs/chap-08-C-asm/bonus-CA-x64/Makefile b/curs/chap-08-C-asm/bonus-CA-x64/Makefile new file mode 100644 index 00000000..89238a41 --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/Makefile @@ -0,0 +1,31 @@ +NASM=nasm +GCC=gcc + +all: asm_call_c c_call_asm inline_asm + +asm_call_c: asm_call_c_main.o asm_call_c_sum.o + $(GCC) asm_call_c_main.o asm_call_c_sum.o -no-pie -o asm_call_c + +asm_call_c_main.o: asm_call_c_main.asm + $(NASM) -felf64 asm_call_c_main.asm -o asm_call_c_main.o + +asm_call_c_sum.o: asm_call_c_sum.c + $(GCC) -c asm_call_c_sum.c -o asm_call_c_sum.o + +c_call_asm: c_call_asm_main.o c_call_asm_sum.o + $(GCC) c_call_asm_main.o c_call_asm_sum.o -no-pie -o c_call_asm + +c_call_asm_main.o: c_call_asm_main.c + $(GCC) -c c_call_asm_main.c -o c_call_asm_main.o + +c_call_asm_sum.o: c_call_asm_sum.asm + $(NASM) -felf64 c_call_asm_sum.asm -o c_call_asm_sum.o + +inline_asm: inline_asm.o + $(GCC) inline_asm.o -o inline_asm + +inline_asm.o: inline_asm.c + $(GCC) -masm=intel -c inline_asm.c -o inline_asm.o + +clean: + rm -f *.o asm_call_c c_call_asm inline_asm diff --git a/curs/chap-08-C-asm/bonus-CA-x64/README.md b/curs/chap-08-C-asm/bonus-CA-x64/README.md new file mode 100644 index 00000000..d4b88afc --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/README.md @@ -0,0 +1,158 @@ +# C <-> ASM Interface Examples (x86-64, System V AMD64) + +This folder contains 3 examples that mirror the style of `func-cdecl`, focused on calling between C and assembly. + +## Files + +- `asm_call_c_main.asm` + `asm_call_c_sum.c` + - Assembly `main` defines the array in `.data`, calls a C function that sums the array, then prints the result. + +- `c_call_asm_main.c` + `c_call_asm_sum.asm` + - C `main` has a globally initialized array and calls an assembly function to compute the sum. + +- `inline_asm.c` + - Single C file that uses GCC inline assembly to sum an array. + +## Build and run + +```bash +make +./asm_call_c +./c_call_asm +./inline_asm +``` + +## Clean + +```bash +make clean +``` + +--- + +## GCC Extended Inline Assembly + +### Basic structure + +```c +__asm__ volatile ( + "asm instructions" + : outputs + : inputs + : clobbers +); +``` + +Each section is separated by `:`. Any section can be empty (just leave it blank or omit trailing colons). + +--- + +### Why `volatile`? + +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. + +`volatile` suppresses all of that: +- The block is **always emitted**, even if the output is unused. +- The block is **not moved** relative to other memory operations. +- Multiple identical blocks are **not merged** into one. + +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. + +--- + +### Outputs + +```c +: "=&r" (sum) +``` + +Each output is `"constraint" (c_variable)`. The variable receives the register value after the block. + +| Modifier | Meaning | +|---|---| +| `=` | write-only - value on entry is discarded | +| `+` | read-write - value is read on entry and written on exit | +| `&` | early-clobber - this register is written before all inputs are consumed; GCC must not share it with any input | + +--- + +### Inputs + +```c +: "r" (arr), "r" (n) +``` + +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`, ... + +--- + +### Operand constraints + +| Constraint | Location | +|---|---| +| `r` | any general-purpose register (GCC chooses) | +| `a` | `rax` / `eax` | +| `b` | `rbx` / `ebx` | +| `c` | `rcx` / `ecx` | +| `d` | `rdx` / `edx` | +| `S` | `rsi` / `esi` | +| `D` | `rdi` / `edi` | +| `m` | memory operand | +| `i` | immediate integer constant | + +With `r`, GCC picks the register - use size modifiers in the asm string to get the right-width name: + +| Modifier | Register size | Example (`%0` → `rsi`) | +|---|---|---| +| `%q0` | 64-bit | `rsi` | +| `%k0` | 32-bit | `esi` | +| `%w0` | 16-bit | `si` | +| `%b0` | 8-bit | `sil` | + +With specific constraints (`a`, `c`, `S`, ...) you know the register name and can write it directly. + +--- + +### Named operands + +Instead of positional `%0`, `%1`, you can use names: + +```c +__asm__ volatile ( + "add %k[sum], dword ptr [%q[arr] + r8 * 4]" + : [sum] "=&r" (sum) + : [arr] "r" (arr), [n] "r" (n) + : "r8", "cc", "memory" +); +``` + +--- + +### Clobbers + +Declare every register and resource the asm modifies that is not an output: + +| Clobber | Meaning | +|---|---| +| `"rax"`, `"r8"`, ... | asm modifies this register | +| `"cc"` | asm modifies RFLAGS (`add`, `sub`, `cmp`, `test`, `inc`, `dec`, ...) | +| `"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 | + +Omitting a clobber is undefined behaviour: GCC may place a live value in that register and your asm will silently corrupt it. + +--- + +### Numeric labels + +Use numbers instead of named labels to avoid collisions when the same asm block is inlined multiple times: + +```c +"test ecx, ecx\n\t" +"jle 2f\n\t" // jump forward to label 2 +"1:\n\t" // loop top +" ...\n\t" +"jl 1b\n\t" // jump backward to label 1 +"2:\n\t" // exit +``` + +`f` = forward, `b` = backward. The same number can appear many times; the direction disambiguates which instance is the target. diff --git a/curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_main.asm b/curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_main.asm new file mode 100644 index 00000000..7c656b94 --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_main.asm @@ -0,0 +1,26 @@ +section .data +values: dd 7, -2, 10, 5, 4 +values_len: equ ($ - values) / 4 +fmt_print: db "asm_call_c sum = %d", 10, 0 + +section .text +global main +extern sum_array_c +extern printf + +main: + push rbp + mov rbp, rsp + + lea rdi, [rel values] + mov esi, values_len + call sum_array_c + + mov esi, eax + lea rdi, [rel fmt_print] + xor eax, eax + call printf + + xor eax, eax + leave + ret diff --git a/curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_sum.c b/curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_sum.c new file mode 100644 index 00000000..20c13a1a --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/asm_call_c_sum.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BSD-3-Clause +#include + +int sum_array_c(const int *arr, int n) +{ + int sum = 0; + + for (int i = 0; i < n; i++) + sum += arr[i]; + + return sum; +} diff --git a/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_main.c b/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_main.c new file mode 100644 index 00000000..d0b9512c --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_main.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause +#include + +#include "c_call_asm_sum.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +int g_values[] = {3, 4, -1, 9, 12, 5}; +int g_values_len = (int)ARRAY_SIZE(g_values); + +int main(void) +{ + int sum = sum_array_asm(g_values, g_values_len); + + printf("c_call_asm sum = %d\n", sum); + return 0; +} diff --git a/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.asm b/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.asm new file mode 100644 index 00000000..85fdd21e --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.asm @@ -0,0 +1,20 @@ +section .text +global sum_array_asm + +; int sum_array_asm(const int *arr, int n) +; arr -> RDI, n -> ESI, return -> EAX +sum_array_asm: + xor eax, eax + xor edx, edx + + test esi, esi + jle .done + +.loop: + add eax, dword [rdi + rdx * 4] + inc edx + cmp edx, esi + jl .loop + +.done: + ret diff --git a/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.h b/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.h new file mode 100644 index 00000000..e61cb40e --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/c_call_asm_sum.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef C_CALL_ASM_SUM_H +#define C_CALL_ASM_SUM_H + +int sum_array_asm(const int *arr, int n); + +#endif /* C_CALL_ASM_SUM_H */ diff --git a/curs/chap-08-C-asm/bonus-CA-x64/inline_asm.c b/curs/chap-08-C-asm/bonus-CA-x64/inline_asm.c new file mode 100644 index 00000000..56c114ac --- /dev/null +++ b/curs/chap-08-C-asm/bonus-CA-x64/inline_asm.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSD-3-Clause +#include + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +static int sum_array_inline(const int *arr, int n) +{ + int sum; + + /* %0 = sum (output, eax), %1 = arr (pointer), %2 = n (int) */ + __asm__ volatile ( + "xor %k0, %k0\n\t" /* sum = 0 */ + "xor r8d, r8d\n\t" /* index i = 0 */ + "test %k2, %k2\n\t" /* if (n <= 0) skip */ + "jle 2f\n\t" + "1:\n\t" + "add %k0, dword ptr [%q1 + r8 * 4]\n\t" /* sum += arr[i] */ + "inc r8d\n\t" /* i++ */ + "cmp r8d, %k2\n\t" /* if (i < n) loop */ + "jl 1b\n\t" + "2:\n\t" + : "=&r" (sum) + : "r" (arr), "r" (n) + : "r8", "cc", "memory" + ); + + return sum; +} + +int main(void) +{ + int values[] = {8, 1, -3, 20, 4}; + int n = (int)ARRAY_SIZE(values); + int sum = sum_array_inline(values, n); + + printf("inline_asm sum = %d\n", sum); + + return 0; +}