- Binary:
hello - Source:
hello.c(standard "Hello, World!" program) - Tool: Radare2 v5.5.0
- Architecture: x86-64 (Linux, PIE enabled)
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}r2 identified 10 functions in the binary:
| Address | Size | Name | Description |
|---|---|---|---|
0x00001000 |
27 | sym._init |
ELF init section |
0x00001040 |
10 | fcn.00001040 |
PLT stub (lazy resolver) |
0x00001050 |
10 | sym.imp.puts |
PLT stub for puts() from libc |
0x00001060 |
38 | entry0 |
ELF entry point (_start) |
0x00001090 |
41 | sym.deregister_tm_clones |
glibc TLS cleanup |
0x000010c0 |
57 | sym.register_tm_clones |
glibc TLS setup |
0x00001100 |
57 | sym.__do_global_dtors_aux |
Destructor runner |
0x00001140 |
9 | entry.init0 |
frame_dummy (init array) |
0x00001149 |
30 | main |
User code |
0x00001168 |
13 | sym._fini |
ELF fini section |
Most of these are CRT (C Runtime) boilerplate — inserted automatically by the linker and glibc. The only user-written function is main.
┌ 30: int main (int argc, char **argv, char **envp);
│
│ 0x00001149 f30f1efa endbr64
│ 0x0000114d 55 push rbp
│ 0x0000114e 4889e5 mov rbp, rsp
│ 0x00001151 488d05ac0e00. lea rax, str.Hello__World_ ; 0x2004 ; "Hello, World!"
│ 0x00001158 4889c7 mov rdi, rax ; const char *s
│ 0x0000115b e8f0feffff call sym.imp.puts ; int puts(const char *s)
│ 0x00001160 b800000000 mov eax, 0
│ 0x00001165 5d pop rbp
│ 0x00001166 c3 ret
└- Opcode:
f3 0f 1e fa - This is an Intel Control-flow Enforcement Technology (CET) instruction. It marks this
address as a valid target for indirect branches (
callvia function pointer,jmpvia vtable, etc.). - If CET is enabled in hardware and an indirect branch lands somewhere without
endbr64, the CPU raises a fault. This mitigates ROP (Return-Oriented Programming) attacks. - On CPUs without CET, it is treated as a NOP — harmless and zero overhead.
- Opcode:
55 - Pushes the caller's base pointer (
rbp) onto the stack so it can be restored later. This is the first half of the standard x86-64 function prologue.
- Opcode:
48 89 e5 - Copies the current stack pointer (
rsp) intorbp, establishing a new stack frame. Together withpush rbp, this creates a linked list of stack frames that debuggers (like gdb) and tools (likebacktrace()) walk to produce stack traces. - Since
mainhas no local variables,rspis never decremented further (nosub rsp, N).
- Opcode:
48 8d 05 ac 0e 00 00 - LEA = Load Effective Address. This does not read memory — it computes the address
RIP + 0xeacand stores it inrax. - The result is
0x2004, which is the offset in the.rodatasection where the string"Hello, World!"lives. - r2 helpfully annotates this:
; 0x2004 ; "Hello, World!" - This is RIP-relative addressing — standard in x86-64 position-independent code (PIE). The instruction encodes a relative offset rather than an absolute address, so the binary can be loaded at any base address by ASLR.
- Opcode:
48 89 c7 - In the System V AMD64 ABI (used on Linux), the first function argument is passed in
register
rdi. This moves the pointer to"Hello, World!"intordisoputs()will receive it. - r2 annotates:
const char *s— the parameter name from theputsprototype.
- Opcode:
e8 f0 fe ff ff - Calls
puts()via the PLT (Procedure Linkage Table). The PLT is a trampoline: on the first call, the dynamic linker resolves the real address ofputsin libc and patches the GOT (Global Offset Table). Subsequent calls jump directly to libc. - Compiler optimisation note: The source code says
printf("Hello, World!\n"), but GCC detected that the format string has no%specifiers and ends with\n. It replacedprintf(...)withputs(...)(which appends a newline automatically), saving the overhead of format-string parsing. The trailing\nis stripped from the string — that's why it's"Hello, World!"not"Hello, World!\n".
- Opcode:
b8 00 00 00 00 - Sets
eax(the lower 32 bits ofrax) to0. In the System V ABI, the return value of a function is passed inrax/eax. This corresponds toreturn 0;in the source. - Writing to
eaximplicitly zero-extends to the full 64-bitraxregister (an x86-64 rule), sorax = 0. - The compiler chose
mov eax, 0(5 bytes) overxor eax, eax(2 bytes) here because optimisation is at-O0(default). At-O2, GCC would use the shorterxorform.
- Opcode:
5d - Pops the saved
rbpfrom the stack, restoring the caller's stack frame. This is the function epilogue, paired withpush rbpfrom step 2.
- Opcode:
c3 - Pops the return address off the stack into
RIP, transferring control back to the caller (__libc_start_mainin the CRT, which calledmain). The CRT then callsexit(0)with the valuemainreturned.
| Source Line | Assembly | Purpose |
|---|---|---|
int main(void) { |
endbr64 + push rbp + mov rbp, rsp |
CET landing pad + stack frame setup |
printf("Hello, World!\n"); |
lea rax, "Hello, World!" + mov rdi, rax + call puts |
GCC optimised printf → puts |
return 0; |
mov eax, 0 |
Put 0 in the return register |
} |
pop rbp + ret |
Tear down frame, return to CRT |
The function is 30 bytes total, with no branches — a single straight-line basic block. Clean, minimal, and exactly what you'd expect from an unoptimised "Hello, World!".
Audit generated with Radare2 v5.5.0 on Linux x86-64.