lib/std/arch/rv64/shared/tests.rad 10.8 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 {
48
            throw testing::TestError::Failed;
49
        };
50
    let entry = package.entry else {
51
        throw testing::TestError::Failed;
52
    };
53
    try testing::expect(entry == 0x180000000 and package.code.len == 2);
54
    try testing::expect(package.code[0] == 0x02a00513 and package.exports[0].target == super::Target::Function(entry));
55
}
56
57
/// Packed catalog pointers resolve to retained metadata at the supplied native base.
58
@test unsafe fn nativeCatalog() throws (testing::TestError) {
59
    let package = try compile(imports(), 64) catch {
60
        throw testing::TestError::Failed;
61
    };
62
    let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512]));
63
    let base = output.ptr as u64;
64
    let used = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], base, output)
65
        catch {
66
            throw testing::TestError::Failed;
67
        };
68
    let storage = &CATALOG[0] as *opaque;
69
    let entry = storage as *catalog::Entry;
70
    try testing::expect(used > @sizeOf(catalog::Entry) and used <= output.len);
71
    try testing::expect(mem::eq(entry.source, "trusted binary") and mem::eq(entry.package.name, "p"));
72
    try testing::expect(mem::eq(entry.package.dependencies[0], "dep"));
73
    try testing::expect(entry.package.code.ptr as u64 == package.codeAddress and entry.package.code.len == package.code.len);
74
    try testing::expect(entry.source.ptr as u64 >= base and entry.source.ptr as u64 < base + used as u64);
75
    set TEMPLATE[0] = 91;
76
    set EXPORTS[0].name = "changed";
77
    try testing::expect(entry.package.template[0] == 0 and mem::eq(entry.package.exports[0].name, "p::refs"));
78
    let mut state: [u8; 48] = [255; 48];
79
    try super::instantiate(&entry.package, &[0, 0x180002000, 0x180008000], &mut state[..])
80
        catch {
81
            throw testing::TestError::Failed;
82
        };
83
    try testing::expect(pointer(&state[..], 0) == 0x180004000 and pointer(&state[..], 16) == 0x180001234);
84
    let relocated = try catalog::pack(&[catalog::Entry { source: "trusted binary", package }], 0x180000000, output)
85
        catch {
86
            throw testing::TestError::Failed;
87
        };
88
    try testing::expect(entry.source.ptr as u64 >= 0x180000000 and entry.source.ptr as u64 < 0x180000000 + relocated as u64);
89
}
90
91
/// Catalog bounds and native alignment failures are recoverable.
92
@test unsafe fn nativeCatalogBounds() throws (testing::TestError) {
93
    let package = try compile(imports(), 64) catch {
94
        throw testing::TestError::Failed;
95
    };
96
    let entries = [catalog::Entry { source: "binary", package }];
97
    let output = @sliceOf(&mut CATALOG[0] as *mut u8, @sizeOf([u64; 512]));
98
    let mut failures: u32 = 0;
99
    try catalog::pack(&entries[..], 0x80000000, &mut output[..@sizeOf(catalog::Entry)]) catch err {
100
        try testing::expect(err == super::Error::Capacity); set failures += 1;
101
    };
102
    try catalog::pack(&entries[..], 0x80000001, output) catch err {
103
        try testing::expect(err == super::Error::Alignment); set failures += 1;
104
    };
105
    try catalog::pack(&entries[..], 0xfffffffffffffff8, output) catch err {
106
        try testing::expect(err == super::Error::Range); set failures += 1;
107
    };
108
    try testing::expect(failures == 3);
109
    let used = try catalog::pack(&entries[..], 0x80000000, output) catch {
110
        throw testing::TestError::Failed;
111
    };
112
    try testing::expect(used > @sizeOf(catalog::Entry));
113
}
114
115
/// Build a package with repeated dependency pointers and a high code pointer.
116
unsafe fn input() -> binary::Package {
117
    return binary::Package {
118
        symbols: &[], name: "p", dependencies: &["dep"],
119
        exports: &[binary::Export { name: "p::refs", kind: binary::ExportKind::Data }],
120
        entry: nil,
121
        program: il::Program {
122
            data: &[
123
                il::Data {
124
                    name: "p::refs", size: 32, alignment: 8, readOnly: true, isZeroInit: false,
125
                    values: &[
126
                        il::DataValue { item: il::DataItem::Sym("dep::value"), count: 2 },
127
                        il::DataValue { item: il::DataItem::Fn("dep::call"), count: 1 },
128
                    ],
129
                },
130
                il::Data {
131
                    name: "p::zero", size: 16, alignment: 16, readOnly: false, isZeroInit: true, values: &[],
132
                },
133
            ],
134
            fns: &[],
135
        },
136
    };
137
}
138
139
/// Compile into small persistent tables.
140
unsafe fn compile(imports: &[super::Symbol], capacity: u32) -> super::Package throws (super::Error) {
141
    let mut arena = alloc::new(&mut ARENA[..]);
142
    let mut scratch = alloc::new(&mut SCRATCH[..]);
143
    let package = input();
144
    return try super::compile(&package, 2, 0x180000000, imports,
145
        super::Storage {
146
            data: &mut DATA[..], symbols: &mut SYMBOLS[..], exports: &mut EXPORTS[..],
147
            template: &mut TEMPLATE[..capacity], relocations: &mut RELOCS[..],
148
        }, &mut arena, &mut scratch);
149
}
150
151
/// Dependency exports with a full-width function pointer and a large data offset.
152
fn imports() -> *[super::Symbol] {
153
    return &[
154
        super::Symbol { name: "dep::value", target: super::Target::Data(super::DataRef { slot: 1, offset: 8192 }) },
155
        super::Symbol { name: "dep::call", target: super::Target::Function(0x180001234) },
156
    ];
157
}
158
159
/// Read a little-endian pointer without requiring buffer alignment.
160
fn pointer(bytes: &[u8], offset: u32) -> u64 {
161
    let mut value: u64 = 0;
162
    for i in 0..8 {
163
        set value |= bytes[offset + i] as u64 << (i as u64 * 8);
164
    }
165
    return value;
166
}
167
168
/// Check repeated pointers, full-width code addresses, padding, and zero-filled tails.
169
@test unsafe fn privateRelocations() throws (testing::TestError) {
170
    let package = try compile(imports(), 64) catch {
171
        throw testing::TestError::Failed;
172
    };
173
    try testing::expect(package.template.len == 32 and package.memory == 48 and package.alignment == 16);
174
    try testing::expect(package.relocations.len == 1 and package.relocations[0].count == 2);
175
    try testing::expect(pointer(package.template, 16) == 0x180001234);
176
    let mut first: [u8; 48] = [255; 48];
177
    let mut second: [u8; 48] = [255; 48];
178
    try super::instantiate(&package, &[0, 0x180002000, 0x180008000], &mut first[..])
179
        catch {
180
            throw testing::TestError::Failed;
181
        };
182
    try super::instantiate(&package, &[0, 0x280002000, 0x280008000], &mut second[..])
183
        catch {
184
            throw testing::TestError::Failed;
185
        };
186
    try testing::expect(pointer(&first[..], 0) == 0x180004000 and pointer(&first[..], 8) == 0x180004000);
187
    try testing::expect(pointer(&second[..], 0) == 0x280004000 and pointer(&second[..], 8) == 0x280004000);
188
    try testing::expect(pointer(&first[..], 16) == pointer(&second[..], 16));
189
    for i in 24..48 {
190
        try testing::expect(first[i] == 0 and second[i] == 0);
191
    }
192
}
193
194
/// A missing instance, bad base, or short buffer leaves instance bytes untouched.
195
@test unsafe fn instanceFailures() throws (testing::TestError) {
196
    let package = try compile(imports(), 64) catch {
197
        throw testing::TestError::Failed;
198
    };
199
    let mut bytes: [u8; 48] = [255; 48];
200
    let mut failed: u32 = 0;
201
    try super::instantiate(&package, &[0, 0, 0x8000], &mut bytes[..]) catch err {
202
        try testing::expect(err == super::Error::Instance); set failed += 1;
203
    };
204
    try super::instantiate(&package, &[0, 0x2000, 0x8008], &mut bytes[..]) catch err {
205
        try testing::expect(err == super::Error::Alignment); set failed += 1;
206
    };
207
    try super::instantiate(&package, &[0, 0x2000, 0x8000], &mut bytes[..47]) catch err {
208
        try testing::expect(err == super::Error::Capacity); set failed += 1;
209
    };
210
    try super::instantiate(&package, &[0, 0xffffffffffffffff, 0x8000], &mut bytes[..]) catch err {
211
        try testing::expect(err == super::Error::Range); set failed += 1;
212
    };
213
    try testing::expect(failed == 4);
214
    for byte in &bytes[..] {
215
        try testing::expect(byte == 255);
216
    }
217
}
218
219
/// Missing symbols, duplicate imports, kind mismatches, and short output fail explicitly.
220
@test unsafe fn linkFailures() throws (testing::TestError) {
221
    let mut failed: u32 = 0;
222
    try compile(&[], 64) catch err {
223
        try testing::expect(err == super::Error::Symbol); set failed += 1;
224
    };
225
    let names = imports();
226
    try compile(&[names[0], names[0]], 64) catch err {
227
        try testing::expect(err == super::Error::Symbol); set failed += 1;
228
    };
229
    try compile(&[
230
        super::Symbol { name: "dep::value", target: super::Target::Function(0x8000) }, names[1],
231
    ], 64) catch err {
232
        try testing::expect(err == super::Error::Symbol); set failed += 1;
233
    };
234
    try compile(names, 31) catch err {
235
        try testing::expect(err == super::Error::Capacity); set failed += 1;
236
    };
237
    try testing::expect(failed == 4);
238
    let package = try compile(names, 64) catch {
239
        throw testing::TestError::Failed;
240
    };
241
    try testing::expect(package.exports.len == 1);
242
}