lib/std/arch/rv64/shared/tests.rad 10.5 KiB raw
1
//! Private template relocation and bounded linking checks.
2
3
use std::testing;
4
use std::mem;
5
use std::arch::rv64::shared::catalog;
6
use std::arch::rv64::asm;
7
use std::lang::alloc;
8
use std::lang::il;
9
use std::lang::il::binary;
10
use std::lang::gen::data;
11
12
/// Code generation workspace reused between checks.
13
static ARENA: [u8; 16777216] = [0; 16777216];
14
/// Function allocation workspace.
15
static SCRATCH: [u8; 16777216] = [0; 16777216];
16
/// Persistent catalog symbols.
17
unsafe static SYMBOLS: [super::Symbol; 8] = undefined;
18
/// Persistent exported symbols.
19
unsafe static EXPORTS: [super::Symbol; 8] = undefined;
20
/// Data layout workspace.
21
unsafe static DATA: [data::DataSym; 8] = undefined;
22
/// Persistent initialized bytes.
23
static TEMPLATE: [u8; 64] = [0; 64];
24
/// Persistent private data relocations.
25
unsafe static RELOCS: [super::Relocation; 8] = undefined;
26
/// Aligned storage for a native boot catalog and its retained payloads.
27
static CATALOG: [u64; 512] = [0; 512];
28
29
/// Exported assembly boundaries occupy the same code extent as their package.
30
@test unsafe fn assemblyPrefix() throws (testing::TestError) {
31
    let input = binary::Package {
32
        symbols: &[], name: "p", dependencies: &[],
33
        exports: &[binary::Export { name: "p::entry", kind: binary::ExportKind::Function }],
34
        entry: "p::entry", program: il::Program { data: &[], fns: &[] },
35
    };
36
    let assembly = asm::Program {
37
        text: &[0x02a00513 as u32, 0x00008067], data: &[],
38
        symbols: &[asm::Symbol { name: "p::entry", section: asm::Section::Text, offset: 0, isExported: true }],
39
        externalFixups: &[],
40
    };
41
    let mut arena = alloc::new(&mut ARENA[..]);
42
    let mut scratch = alloc::new(&mut SCRATCH[..]);
43
    let package = try super::compileAssembly(super::AssemblyInput { package: &input, assembly }, 0, 0x180000000, &[],
44
        super::Storage {
45
            data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..],
46
            template: &mut TEMPLATE[..], relocations: &mut RELOCS[..],
47
        }, &mut arena, &mut scratch) catch { throw testing::TestError::Failed; };
48
    let entry = package.entry else { throw testing::TestError::Failed; };
49
    try testing::expect(entry == 0x180000000 and package.code.len == 2);
50
    try testing::expect(package.code[0] == 0x02a00513 and package.exports[0].target == super::Target::Function(entry));
51
}
52
53
/// Packed catalog pointers resolve to retained metadata at the supplied native base.
54
@test unsafe fn nativeCatalog() throws (testing::TestError) {
55
    let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; };
56
    let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512]));
57
    let base = output.ptr as u64;
58
    let used = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], base, output)
59
        catch { throw testing::TestError::Failed; };
60
    let storage = &CATALOG[0] as *opaque;
61
    let entry = storage as *catalog::Entry;
62
    try testing::expect(used > @sizeOf(catalog::Entry) and used <= output.len);
63
    try testing::expect(mem::eq(entry.source, "trusted binary") and mem::eq(entry.package.name, "p"));
64
    try testing::expect(mem::eq(entry.package.dependencies[0], "dep"));
65
    try testing::expect(entry.package.code.ptr as u64 == package.codeAddress and entry.package.code.len == package.code.len);
66
    try testing::expect(entry.source.ptr as u64 >= base and entry.source.ptr as u64 < base + used as u64);
67
    set TEMPLATE[0] = 91;
68
    set EXPORTS[0].name = "changed";
69
    try testing::expect(entry.package.template[0] == 0 and mem::eq(entry.package.exports[0].name, "p::refs"));
70
    let mut state: [u8; 48] = [255; 48];
71
    try super::instantiate(&entry.package, &[0, 0x180002000, 0x180008000], &mut state[..])
72
        catch { throw testing::TestError::Failed; };
73
    try testing::expect(pointer(&state[..], 0) == 0x180004000 and pointer(&state[..], 16) == 0x180001234);
74
    let relocated = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], 0x180000000, output)
75
        catch { throw testing::TestError::Failed; };
76
    try testing::expect(entry.source.ptr as u64 >= 0x180000000 and entry.source.ptr as u64 < 0x180000000 + relocated as u64);
77
}
78
79
/// Catalog bounds and native alignment failures are recoverable.
80
@test unsafe fn nativeCatalogBounds() throws (testing::TestError) {
81
    let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; };
82
    let entries = [catalog::Entry { source: "binary", package }];
83
    let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512]));
84
    let mut failures: u32 = 0;
85
    try catalog::pack(&entries[..], 0x80000000, &mut output[..@sizeOf(catalog::Entry)]) catch err {
86
        try testing::expect(err == super::Error::Capacity); set failures += 1;
87
    };
88
    try catalog::pack(&entries[..], 0x80000001, output) catch err {
89
        try testing::expect(err == super::Error::Alignment); set failures += 1;
90
    };
91
    try catalog::pack(&entries[..], 0xfffffffffffffff8, output) catch err {
92
        try testing::expect(err == super::Error::Range); set failures += 1;
93
    };
94
    try testing::expect(failures == 3);
95
    let used = try catalog::pack(&entries[..], 0x80000000, output) catch { throw testing::TestError::Failed; };
96
    try testing::expect(used > @sizeOf(catalog::Entry));
97
}
98
99
/// Build a package with repeated dependency pointers and a high code pointer.
100
unsafe fn input() -> binary::Package {
101
    return binary::Package {
102
        symbols: &[], name: "p", dependencies: &["dep"],
103
        exports: &[binary::Export { name: "p::refs", kind: binary::ExportKind::Data }],
104
        entry: nil,
105
        program: il::Program {
106
            data: &[
107
                il::Data {
108
                    name: "p::refs", size: 32, alignment: 8, readOnly: true, isZeroInit: false,
109
                    values: &[
110
                        il::DataValue { item: il::DataItem::Sym("dep::value"), count: 2 },
111
                        il::DataValue { item: il::DataItem::Fn("dep::call"), count: 1 },
112
                    ],
113
                },
114
                il::Data {
115
                    name: "p::zero", size: 16, alignment: 16, readOnly: false, isZeroInit: true, values: &[],
116
                },
117
            ],
118
            fns: &[],
119
        },
120
    };
121
}
122
123
/// Compile into small persistent tables.
124
unsafe fn compile(imports: &[super::Symbol], capacity: u32) -> super::Package throws (super::Error) {
125
    let mut arena = alloc::new(&mut ARENA[..]);
126
    let mut scratch = alloc::new(&mut SCRATCH[..]);
127
    let package = input();
128
    return try super::compile(&package, 2, 0x180000000, imports,
129
        super::Storage {
130
            data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..],
131
            template: &mut TEMPLATE[..capacity], relocations: &mut RELOCS[..],
132
        }, &mut arena, &mut scratch);
133
}
134
135
/// Dependency exports with a full-width function pointer and a large data offset.
136
fn imports() -> *[super::Symbol] {
137
    return &[
138
        super::Symbol { name: "dep::value", target: super::Target::Data(super::DataRef { slot: 1, offset: 8192 }) },
139
        super::Symbol { name: "dep::call", target: super::Target::Function(0x180001234) },
140
    ];
141
}
142
143
/// Read a little-endian pointer without requiring buffer alignment.
144
fn pointer(bytes: &[u8], offset: u32) -> u64 {
145
    let mut value: u64 = 0;
146
    for i in 0..8 { set value |= bytes[offset + i] as u64 << (i as u64 * 8); }
147
    return value;
148
}
149
150
/// Check repeated pointers, full-width code addresses, padding, and zero-filled tails.
151
@test unsafe fn privateRelocations() throws (testing::TestError) {
152
    let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; };
153
    try testing::expect(package.template.len == 32 and package.memory == 48 and package.alignment == 16);
154
    try testing::expect(package.relocations.len == 1 and package.relocations[0].count == 2);
155
    try testing::expect(pointer(package.template, 16) == 0x180001234);
156
    let mut first: [u8; 48] = [255; 48];
157
    let mut second: [u8; 48] = [255; 48];
158
    try super::instantiate(&package, &[0, 0x180002000, 0x180008000], &mut first[..])
159
        catch { throw testing::TestError::Failed; };
160
    try super::instantiate(&package, &[0, 0x280002000, 0x280008000], &mut second[..])
161
        catch { throw testing::TestError::Failed; };
162
    try testing::expect(pointer(&first[..], 0) == 0x180004000 and pointer(&first[..], 8) == 0x180004000);
163
    try testing::expect(pointer(&second[..], 0) == 0x280004000 and pointer(&second[..], 8) == 0x280004000);
164
    try testing::expect(pointer(&first[..], 16) == pointer(&second[..], 16));
165
    for i in 24..48 { try testing::expect(first[i] == 0 and second[i] == 0); }
166
}
167
168
/// A missing instance, bad base, or short buffer leaves instance bytes untouched.
169
@test unsafe fn instanceFailures() throws (testing::TestError) {
170
    let package = try compile(imports(), 64) catch { throw testing::TestError::Failed; };
171
    let mut bytes: [u8; 48] = [255; 48];
172
    let mut failed: u32 = 0;
173
    try super::instantiate(&package, &[0, 0, 0x8000], &mut bytes[..]) catch err {
174
        try testing::expect(err == super::Error::Instance); set failed += 1;
175
    };
176
    try super::instantiate(&package, &[0, 0x2000, 0x8008], &mut bytes[..]) catch err {
177
        try testing::expect(err == super::Error::Alignment); set failed += 1;
178
    };
179
    try super::instantiate(&package, &[0, 0x2000, 0x8000], &mut bytes[..47]) catch err {
180
        try testing::expect(err == super::Error::Capacity); set failed += 1;
181
    };
182
    try super::instantiate(&package, &[0, 0xffffffffffffffff, 0x8000], &mut bytes[..]) catch err {
183
        try testing::expect(err == super::Error::Range); set failed += 1;
184
    };
185
    try testing::expect(failed == 4);
186
    for byte in &bytes[..] { try testing::expect(byte == 255); }
187
}
188
189
/// Missing symbols, duplicate imports, kind mismatches, and short output fail explicitly.
190
@test unsafe fn linkFailures() throws (testing::TestError) {
191
    let mut failed: u32 = 0;
192
    try compile(&[], 64) catch err {
193
        try testing::expect(err == super::Error::Symbol); set failed += 1;
194
    };
195
    let names = imports();
196
    try compile(&[names[0], names[0]], 64) catch err {
197
        try testing::expect(err == super::Error::Symbol); set failed += 1;
198
    };
199
    try compile(&[
200
        super::Symbol { name: "dep::value", target: super::Target::Function(0x8000) }, names[1],
201
    ], 64) catch err {
202
        try testing::expect(err == super::Error::Symbol); set failed += 1;
203
    };
204
    try compile(names, 31) catch err {
205
        try testing::expect(err == super::Error::Capacity); set failed += 1;
206
    };
207
    try testing::expect(failed == 4);
208
    let package = try compile(names, 64) catch { throw testing::TestError::Failed; };
209
    try testing::expect(package.exports.len == 1);
210
}