summaryrefslogtreecommitdiff
path: root/tests/test_symbols.zig
blob: 55028e49b0fa407bc1596199979df3846eab4fd5 (plain) (blame)
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;
    }
}