1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
const std = @import("std");
const elfo = @import("elfo");
/// Build-system test that parses a controlled C binary and asserts correct
/// symbol resolution — static symbols, dynamic symbols, and PLT entries.
pub fn main(init: std.process.Init.Minimal) !void {
var debug_alloc: std.heap.DebugAllocator(.{}) = .init;
const gpa = debug_alloc.allocator();
var threaded: std.Io.Threaded = .init(gpa, .{
.argv0 = .init(init.args),
.environ = init.environ,
});
defer threaded.deinit();
const io = threaded.io();
var args = init.args.iterate();
_ = args.skip(); // argv[0]
const path = args.next() orelse return error.MissingArg;
const buffer = try gpa.alignedAlloc(u8, std.mem.Alignment.of(u64), 1024 * 100);
defer gpa.free(buffer);
const f = try std.Io.Dir.cwd().openFile(io, path, .{ .mode = .read_only });
var reader = f.reader(io, buffer);
const header = try std.elf.Header.read(&reader.interface);
var sections = try elfo.collectSections(header, &reader, gpa);
defer sections.deinit(gpa);
const shstrtab_data: ?[]u8 = if (sections.shstrtab) |s| blk: {
try reader.seekTo(s.sh_offset);
break :blk try reader.interface.readAlloc(gpa, s.sh_size);
} else null;
defer if (shstrtab_data) |d| gpa.free(d);
const strtab_data: ?[]u8 = if (sections.strtab) |s| blk: {
try reader.seekTo(s.sh_offset);
break :blk try reader.interface.readAlloc(gpa, s.sh_size);
} else null;
defer if (strtab_data) |d| gpa.free(d);
const dynstr_data: ?[]u8 = if (sections.dynstr) |s| blk: {
try reader.seekTo(s.sh_offset);
break :blk try reader.interface.readAlloc(gpa, s.sh_size);
} else null;
defer if (dynstr_data) |d| gpa.free(d);
var symbols = try elfo.collectSymbols(gpa, header, &reader, strtab_data, dynstr_data, shstrtab_data, sections);
defer {
for (symbols.items) |sym| gpa.free(sym.name);
symbols.deinit(gpa);
}
// --- Assertions ---
// 1. Key sections must exist
try assert(sections.symtab != null, ".symtab must exist");
try assert(sections.dynsym != null, ".dynsym must exist");
try assert(sections.strtab != null, ".strtab must exist (via sh_link)");
try assert(sections.dynstr != null, ".dynstr must exist (via sh_link)");
// 2. Static function symbols from .symtab
try assertSymbol(symbols.items, "main", .{ .is_func = true, .nonzero_addr = true });
try assertSymbol(symbols.items, "a", .{ .is_func = true, .nonzero_addr = true });
try assertSymbol(symbols.items, "bcd", .{ .is_func = true, .nonzero_addr = true });
// 3. Dynamic symbols from .dynsym (via .dynstr - clean names without version)
try assertSymbol(symbols.items, "printf", .{});
try assertSymbol(symbols.items, "malloc", .{});
try assertSymbol(symbols.items, "free", .{});
// 4. PLT symbols (synthesized from .rela.plt)
try assertSymbol(symbols.items, "printf@plt", .{ .is_func = true, .nonzero_addr = true });
try assertSymbol(symbols.items, "malloc@plt", .{ .is_func = true, .nonzero_addr = true });
try assertSymbol(symbols.items, "free@plt", .{ .is_func = true, .nonzero_addr = true });
// 5. strtab and dynstr must be different sections (sh_link resolved correctly)
if (sections.strtab) |st| {
if (sections.dynstr) |ds| {
try assert(st.sh_offset != ds.sh_offset, ".strtab and .dynstr must be different sections");
}
}
// success — no output means no warnings from the build system
}
const AssertOpts = struct {
is_func: bool = false,
nonzero_addr: bool = false,
};
fn assertSymbol(symbols: []const elfo.SymbolRange, name: []const u8, opts: AssertOpts) !void {
for (symbols) |sym| {
if (std.mem.eql(u8, sym.name, name)) {
if (opts.is_func and sym.kind != std.elf.STT_FUNC) {
std.debug.print("FAIL: '{s}' expected STT_FUNC, got {d}\n", .{ name, sym.kind });
return error.TestFailed;
}
if (opts.nonzero_addr and sym.start == 0) {
std.debug.print("FAIL: '{s}' expected nonzero address\n", .{name});
return error.TestFailed;
}
return; // found
}
}
std.debug.print("FAIL: symbol '{s}' not found. available symbols:\n", .{name});
for (symbols) |sym| {
std.debug.print(" {x}-{x} kind={d} '{s}'\n", .{ sym.start, sym.end, sym.kind, sym.name });
}
return error.SymbolNotFound;
}
fn assert(condition: bool, msg: []const u8) !void {
if (!condition) {
std.debug.print("FAIL: {s}\n", .{msg});
return error.TestFailed;
}
}
|