Landin library reference discuss source

compiler/tests/fixtures/runtime/linux-vdso/main.ldn

1--  The vDSO the auxiliary vector names: platform/linux finds its
2--  `clock_gettime` by name and version, which reads the same clocks the
3--  system call does, and a copy of the image, damaged one byte at a time
4--  or cut short anywhere, finds nothing or something inside it and never
5--  traps, so a malformed vDSO falls back to the system call.  Without its
6--  version tables the name alone decides, and without DT_HASH the GNU
7--  hash table counts the symbols.  Each check that fails returns its own
8--  code.
9import platform/linux
10import platform/linux/vdso
11
12--  Room for a copy of the image: the kernel's is two pages or so.
13mut copy: [65536]u8
14
15--  The image's length in the copy.
16mut copied: usize
17
18--  The clock `clock` read through the system call, in nanoseconds.
19by_system_call: (clock: i32) -> (at: i64) =
20    mut record: linux.timespec = zeroed
21    at = -1
22    return when linux.clock_gettime(clock, addr record) <> 0
23    at = record.seconds * 1000000000 + record.nanoseconds
24end by_system_call
25
26--  The clock `clock` read through the vDSO function at ADDRESS.
27by_vdso: (address: usize, clock: i32) -> (at: i64) =
28    function: vdso.clock_gettime = vdso.clock_gettime(address)
29    mut record: linux.timespec = zeroed
30    at = -1
31    return when function(clock, addr record) <> 0
32    at = record.seconds * 1000000000 + record.nanoseconds
33end by_vdso
34
35--  Readings through the vDSO fall between system calls made before and
36--  after them, on the monotonic clock, and the realtime clocks agree
37--  within a second.
38agrees: (address: usize) -> (yes: bool) =
39    yes = false
40    for round in usize(0)..<100 do
41        _ = round
42        before: i64 = by_system_call(linux.clock_monotonic)
43        between: i64 = by_vdso(address, linux.clock_monotonic)
44        after: i64 = by_system_call(linux.clock_monotonic)
45        return when before < 0 or between < before or after < between
46    end for
47    wall: i64 = by_vdso(address, linux.clock_realtime)
48    kernel: i64 = by_system_call(linux.clock_realtime)
49    return when wall < 0 or kernel < wall or kernel - wall > 1000000000
50    yes = true
51end agrees
52
53--  Whether a lookup in the first LENGTH bytes of the copy returns without
54--  trapping and, when it finds the clock, finds it inside them.
55holds: (length: usize) -> (yes: bool) =
56    offset: usize = found_at(copy[0..<length], vdso.clock_symbol,
57                             vdso.clock_version)
58    yes = offset == no_offset or offset < length
59end holds
60
61--  Every byte of the copy set to zero, to every bit, and to itself with
62--  its top bit flipped, one at a time and put back after.
63damaged: () -> (yes: bool) =
64    yes = false
65    for at in usize(0)..<copied do
66        kept: u8 = copy[at]
67        copy[at] = 0
68        return when not holds(copied)
69        copy[at] = 255
70        return when not holds(copied)
71        copy[at] = kept ^ 128
72        return when not holds(copied)
73        copy[at] = kept
74    end for
75    yes = true
76end damaged
77
78--  The copy cut short at every length.
79truncated: () -> (yes: bool) =
80    yes = false
81    for length in usize(0)..<copied do
82        return when not holds(length)
83    end for
84    yes = true
85end truncated
86
87--  The little-endian u64 at AT in the copy, and writing one.
88read_double: (at: usize) -> (value: u64) =
89    value = 0
90    for index in usize(0)..<8 do
91        value = value | (u64(copy[at + index]) << u64(8 * index))
92    end for
93end read_double
94
95write_double: (at: usize, value: u64) -> none =
96    for index in usize(0)..<8 do
97        copy[at + index] = u8((value >> u64(8 * index)) & 255)
98    end for
99end write_double
100
101--  Where in the copy the dynamic entry tagged TAG lies, or zero.
102entry_of: (tag: u64) -> (at: usize) =
103    at = 0
104    headers: usize = usize(read_double(32))
105    count: usize = usize(copy[56]) | (usize(copy[57]) << 8)
106    for index in usize(0)..<count do
107        header: usize = headers + index * 56
108        if copy[header] == 2 and copy[header + 1] == 0 then
109            dynamic: usize = usize(read_double(header + 8))
110            mut entry: usize = dynamic
111            loop do
112                found: u64 = read_double(entry)
113                return when found == 0
114                if found == tag then
115                    at = entry
116                    return
117                end if
118                entry += 16
119            end loop
120        end if
121    end for
122end entry_of
123
124--  A tag no dynamic loader reads, which hides the entry it replaces.
125hidden: u64 = 0x6fffff00
126
127--  Where in BYTES the function NAME of VERSION is, or no_offset.
128no_offset: usize = 0 -% 1
129
130found_at: (bytes: []u8, name: []u8, version: []u8) -> (offset: usize) =
131    (found, offset: at) := vdso.find(bytes, name, version)
132    offset = if found then at else no_offset end if
133end found_at
134
135--  Where the auxiliary vector says the vDSO is.
136vdso_base: () -> (base: usize) =
137    (found: _, value) := linux.auxiliary(vdso.at_sysinfo_ehdr)
138    base = usize(value)
139end vdso_base
140
141--  The vDSO's function NAME of VERSION's address, or zero.
142address_of: (name: []u8, version: []u8) -> (address: usize) =
143    (found, address: at) := vdso.symbol(name, version)
144    address = if found then at else 0 end if
145end address_of
146
147public main: () -> (code: i32) =
148    (found: present, image) := vdso.kernel_image()
149    code = 1
150    return when not present or lenof image < 64 or lenof image > 65536
151    code = 2
152    name: []u8 = vdso.clock_symbol
153    version: []u8 = vdso.clock_version
154    offset: usize = found_at(image, name, version)
155    return when offset >= lenof image
156    address: usize = address_of(name, version)
157    return when address <> vdso_base() + offset
158    code = 3
159    return when not agrees(address)
160    code = 4
161    return when found_at(image, name, "LINUX_0.1") <> no_offset
162      or found_at(image, "__vdso_no_such_function", version) <> no_offset
163    code = 5
164    copied = lenof image
165    for at in usize(0)..<copied do
166        copy[at] = image[at]
167    end for
168    return when not damaged()
169    code = 6
170    return when not truncated()
171    code = 7
172    --  Without DT_HASH the GNU hash table counts the symbols, and the same
173    --  function is found where it was.
174    hash: usize = entry_of(4)
175    if hash <> 0 and entry_of(0x6ffffef5) <> 0 then
176        write_double(hash, hidden)
177        return when found_at(copy[0..<copied], name, version) <> offset
178        write_double(hash, 4)
179    end if
180    code = 8
181    --  Without version definitions, the name alone decides.
182    definitions: usize = entry_of(0x6ffffffc)
183    return when definitions == 0
184    write_double(definitions, hidden)
185    return when found_at(copy[0..<copied], name, "LINUX_0.1") <> offset
186    code = 42
187end main