platform/linux/vdso
Target scope is enforced by the module assertion below.
The vDSO, the small shared object Linux maps into every process, and the functions found in it, raw.
A submodule of platform/linux, so a program that never reads a clock this way does not carry it; it declares nothing on Darwin and FreeBSD (D346). The vDSO the kernel maps into every process is found through the auxiliary vector's AT_SYSINFO_EHDR: vdso.kernel_image is its bytes as far as its first load segment reaches, the image the kernel mapped, vdso.find finds a global or weak function by name and version in any image of a 64-bit little-endian ELF object, through its program headers, dynamic table, DT_HASH or DT_GNU_HASH, symbols and version definitions, and vdso.symbol gives the function's address. vdso.clock_symbol and vdso.clock_version name its clock_gettime, __vdso_clock_gettime of LINUX_2.6 on x86-64, __kernel_clock_gettime of LINUX_2.6.39 on arm64 and __vdso_clock_gettime of LINUX_4.15 on RV64, and vdso.clock_gettime is its C type. Every offset is held to lie within the image before a byte is read, so a malformed, truncated or foreign image finds nothing and never traps; runtime/linux-vdso damages a copy of the real one byte by byte to show it.
compiler.assert(compiler.os == linux or compiler.os == darwin
or compiler.os == freebsd)Items
- clock_gettime type
- at_sysinfo_ehdr value
- clock_symbol value
- clock_version value
- largest value
- find function
- kernel_image function
- symbol function
Executable example
This complete program is maintained in the repository runtime tests. View source.
-- The vDSO the auxiliary vector names: platform/linux finds its -- `clock_gettime` by name and version, which reads the same clocks the -- system call does, and a copy of the image, damaged one byte at a time -- or cut short anywhere, finds nothing or something inside it and never -- traps, so a malformed vDSO falls back to the system call. Without its -- version tables the name alone decides, and without DT_HASH the GNU -- hash table counts the symbols. Each check that fails returns its own -- code. import platform/linux import platform/linux/vdso -- Room for a copy of the image: the kernel's is two pages or so. mut copy: [65536]u8 -- The image's length in the copy. mut copied: usize -- The clock `clock` read through the system call, in nanoseconds. by_system_call: (clock: i32) -> (at: i64) = mut record: linux.timespec = zeroed at = -1 return when linux.clock_gettime(clock, addr record) <> 0 at = record.seconds * 1000000000 + record.nanoseconds end by_system_call -- The clock `clock` read through the vDSO function at ADDRESS. by_vdso: (address: usize, clock: i32) -> (at: i64) = function: vdso.clock_gettime = vdso.clock_gettime(address) mut record: linux.timespec = zeroed at = -1 return when function(clock, addr record) <> 0 at = record.seconds * 1000000000 + record.nanoseconds end by_vdso -- Readings through the vDSO fall between system calls made before and -- after them, on the monotonic clock, and the realtime clocks agree -- within a second. agrees: (address: usize) -> (yes: bool) = yes = false for round in usize(0)..<100 do _ = round before: i64 = by_system_call(linux.clock_monotonic) between: i64 = by_vdso(address, linux.clock_monotonic) after: i64 = by_system_call(linux.clock_monotonic) return when before < 0 or between < before or after < between end for wall: i64 = by_vdso(address, linux.clock_realtime) kernel: i64 = by_system_call(linux.clock_realtime) return when wall < 0 or kernel < wall or kernel - wall > 1000000000 yes = true end agrees -- Whether a lookup in the first LENGTH bytes of the copy returns without -- trapping and, when it finds the clock, finds it inside them. holds: (length: usize) -> (yes: bool) = offset: usize = found_at(copy[0..<length], vdso.clock_symbol, vdso.clock_version) yes = offset == no_offset or offset < length end holds -- Every byte of the copy set to zero, to every bit, and to itself with -- its top bit flipped, one at a time and put back after. damaged: () -> (yes: bool) = yes = false for at in usize(0)..<copied do kept: u8 = copy[at] copy[at] = 0 return when not holds(copied) copy[at] = 255 return when not holds(copied) copy[at] = kept ^ 128 return when not holds(copied) copy[at] = kept end for yes = true end damaged -- The copy cut short at every length. truncated: () -> (yes: bool) = yes = false for length in usize(0)..<copied do return when not holds(length) end for yes = true end truncated -- The little-endian u64 at AT in the copy, and writing one. read_double: (at: usize) -> (value: u64) = value = 0 for index in usize(0)..<8 do value = value | (u64(copy[at + index]) << u64(8 * index)) end for end read_double write_double: (at: usize, value: u64) -> none = for index in usize(0)..<8 do copy[at + index] = u8((value >> u64(8 * index)) & 255) end for end write_double -- Where in the copy the dynamic entry tagged TAG lies, or zero. entry_of: (tag: u64) -> (at: usize) = at = 0 headers: usize = usize(read_double(32)) count: usize = usize(copy[56]) | (usize(copy[57]) << 8) for index in usize(0)..<count do header: usize = headers + index * 56 if copy[header] == 2 and copy[header + 1] == 0 then dynamic: usize = usize(read_double(header + 8)) mut entry: usize = dynamic loop do found: u64 = read_double(entry) return when found == 0 if found == tag then at = entry return end if entry += 16 end loop end if end for end entry_of -- A tag no dynamic loader reads, which hides the entry it replaces. hidden: u64 = 0x6fffff00 -- Where in BYTES the function NAME of VERSION is, or no_offset. no_offset: usize = 0 -% 1 found_at: (bytes: []u8, name: []u8, version: []u8) -> (offset: usize) = (found, offset: at) := vdso.find(bytes, name, version) offset = if found then at else no_offset end if end found_at -- Where the auxiliary vector says the vDSO is. vdso_base: () -> (base: usize) = (found: _, value) := linux.auxiliary(vdso.at_sysinfo_ehdr) base = usize(value) end vdso_base -- The vDSO's function NAME of VERSION's address, or zero. address_of: (name: []u8, version: []u8) -> (address: usize) = (found, address: at) := vdso.symbol(name, version) address = if found then at else 0 end if end address_of public main: () -> (code: i32) = (found: present, image) := vdso.kernel_image() code = 1 return when not present or lenof image < 64 or lenof image > 65536 code = 2 name: []u8 = vdso.clock_symbol version: []u8 = vdso.clock_version offset: usize = found_at(image, name, version) return when offset >= lenof image address: usize = address_of(name, version) return when address <> vdso_base() + offset code = 3 return when not agrees(address) code = 4 return when found_at(image, name, "LINUX_0.1") <> no_offset or found_at(image, "__vdso_no_such_function", version) <> no_offset code = 5 copied = lenof image for at in usize(0)..<copied do copy[at] = image[at] end for return when not damaged() code = 6 return when not truncated() code = 7 -- Without DT_HASH the GNU hash table counts the symbols, and the same -- function is found where it was. hash: usize = entry_of(4) if hash <> 0 and entry_of(0x6ffffef5) <> 0 then write_double(hash, hidden) return when found_at(copy[0..<copied], name, version) <> offset write_double(hash, 4) end if code = 8 -- Without version definitions, the name alone decides. definitions: usize = entry_of(0x6ffffffc) return when definitions == 0 write_double(definitions, hidden) return when found_at(copy[0..<copied], name, "LINUX_0.1") <> offset code = 42 end main
clock_gettime type
When compiler.os == linux
public clock_gettime: type = extern(c) (clock: i32, into: ptr mut linux.timespec) -> (result: i32)platform/linux/vdso/vdso.ldn:41
The vDSO's clock_gettime, a C function: it fills into with the clock clock's time and returns zero, or minus the errno, making the system call itself for a clock it cannot read.
at_sysinfo_ehdr value
When compiler.os == linux
public at_sysinfo_ehdr: u64 = 33platform/linux/vdso/vdso.ldn:19
The auxiliary-vector tag of the vDSO's address, AT_SYSINFO_EHDR.
clock_symbol value
When compiler.os == linux; compiler.arch == x86_64
public clock_symbol: []u8 = "__vdso_clock_gettime"platform/linux/vdso/vdso.ldn:23
The name the vDSO gives clock_gettime on this architecture.
When compiler.os == linux; not (compiler.arch == x86_64) and (compiler.arch == arm64)
public clock_symbol: []u8 = "__kernel_clock_gettime"platform/linux/vdso/vdso.ldn:28
The name the vDSO gives clock_gettime on this architecture.
When compiler.os == linux; not (compiler.arch == x86_64 or compiler.arch == arm64)
public clock_symbol: []u8 = "__vdso_clock_gettime"platform/linux/vdso/vdso.ldn:33
The name the vDSO gives clock_gettime on this architecture.
clock_version value
When compiler.os == linux; compiler.arch == x86_64
public clock_version: []u8 = "LINUX_2.6"platform/linux/vdso/vdso.ldn:25
The symbol version it has: x86-64's is LINUX_2.6.
When compiler.os == linux; not (compiler.arch == x86_64) and (compiler.arch == arm64)
public clock_version: []u8 = "LINUX_2.6.39"platform/linux/vdso/vdso.ldn:30
The symbol version it has: arm64's is LINUX_2.6.39.
When compiler.os == linux; not (compiler.arch == x86_64 or compiler.arch == arm64)
public clock_version: []u8 = "LINUX_4.15"platform/linux/vdso/vdso.ldn:35
The symbol version it has: RV64's is LINUX_4.15.
largest value
When compiler.os == linux
public largest: usize = 1048576platform/linux/vdso/vdso.ldn:45
The most of an image symbol believes: the kernel's vDSO is a few pages, and a load segment claiming more is not one.
find function
When compiler.os == linux
public link(symbol: "_landin_linux_vdso_find") find:
(image: []u8, name: []u8, version: []u8)
-> (found: bool, offset: usize)platform/linux/vdso/vdso.ldn:229
Find the function name of version version in image, the bytes of a shared object as it is mapped: whether it is there, and its offset from the image's start. A global or weak function defined in the image counts; where the image has no version tables, the name alone decides, as a dynamic loader decides it. Every offset is held to lie within the image before it is read, so an image that is not a 64-bit little-endian ELF object, is truncated or contradicts itself finds nothing and never traps.
kernel_image function
When compiler.os == linux
public link(symbol: "_landin_linux_vdso_image") kernel_image:
() -> (found: bool, image: []u8)platform/linux/vdso/vdso.ldn:317
The vDSO as the kernel mapped it: whether the auxiliary vector names one, and its bytes, as far as its first load segment reaches and no further than largest. There is none when auxiliary found no auxiliary vector, as when a C main built the tables it passed the argument initializer itself, or when the image's first page does not describe a load segment.
symbol function
When compiler.os == linux
public link(symbol: "_landin_linux_vdso_symbol") symbol:
(name: []u8, version: []u8) -> (found: bool, address: usize)platform/linux/vdso/vdso.ldn:337
The address of the vDSO's function name of version version, found by find in kernel_image: whether it is there, and where.