Last active
April 2, 2025 17:35
-
-
Save mildsunrise/c63505931534bd3c0e143c0db8cad3f3 to your computer and use it in GitHub Desktop.
dumps the data in the vdso_data VVAR (timekeeping info for gettimeofday vDSO)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WARNING: only for x86 | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <time.h> | |
#include <sys/auxv.h> | |
#include <sys/mman.h> | |
#include <sys/syscall.h> | |
#include <sys/prctl.h> | |
#include <linux/seccomp.h> | |
#include <x86intrin.h> | |
// copied from <linux/time.h> | |
#define CLOCK_REALTIME 0 | |
#define CLOCK_MONOTONIC 1 | |
#define CLOCK_PROCESS_CPUTIME_ID 2 | |
#define CLOCK_THREAD_CPUTIME_ID 3 | |
#define CLOCK_MONOTONIC_RAW 4 | |
#define CLOCK_REALTIME_COARSE 5 | |
#define CLOCK_MONOTONIC_COARSE 6 | |
#define CLOCK_BOOTTIME 7 | |
#define CLOCK_REALTIME_ALARM 8 | |
#define CLOCK_BOOTTIME_ALARM 9 | |
#define CLOCK_SGI_CYCLE 10 | |
#define CLOCK_TAI 11 | |
#define MAX_CLOCKS 16 | |
#define CLOCKS_MASK (CLOCK_REALTIME | CLOCK_MONOTONIC) | |
#define CLOCKS_MONO CLOCK_MONOTONIC | |
// adapted from include/vdso/datapage.h | |
#include <linux/types.h> | |
struct arch_vdso_data {}; | |
#define VDSO_BASES (CLOCK_TAI + 1) | |
#define VDSO_HRES (BIT(CLOCK_REALTIME) | \ | |
BIT(CLOCK_MONOTONIC) | \ | |
BIT(CLOCK_BOOTTIME) | \ | |
BIT(CLOCK_TAI)) | |
#define VDSO_COARSE (BIT(CLOCK_REALTIME_COARSE) | \ | |
BIT(CLOCK_MONOTONIC_COARSE)) | |
#define VDSO_RAW (BIT(CLOCK_MONOTONIC_RAW)) | |
#define CS_HRES_COARSE 0 | |
#define CS_RAW 1 | |
#define CS_BASES (CS_RAW + 1) | |
/** | |
* struct vdso_timestamp - basetime per clock_id | |
* @sec: seconds | |
* @nsec: nanoseconds | |
* | |
* There is one vdso_timestamp object in vvar for each vDSO-accelerated | |
* clock_id. For high-resolution clocks, this encodes the time | |
* corresponding to vdso_data.cycle_last. For coarse clocks this encodes | |
* the actual time. | |
* | |
* To be noticed that for highres clocks nsec is left-shifted by | |
* vdso_data.cs[x].shift. | |
*/ | |
struct vdso_timestamp { | |
__u64 sec; | |
__u64 nsec; | |
}; | |
/** | |
* struct vdso_data - vdso datapage representation | |
* @seq: timebase sequence counter | |
* @clock_mode: clock mode | |
* @cycle_last: timebase at clocksource init | |
* @mask: clocksource mask | |
* @mult: clocksource multiplier | |
* @shift: clocksource shift | |
* @basetime[clock_id]: basetime per clock_id | |
* @offset[clock_id]: time namespace offset per clock_id | |
* @tz_minuteswest: minutes west of Greenwich | |
* @tz_dsttime: type of DST correction | |
* @hrtimer_res: hrtimer resolution | |
* @__unused: unused | |
* @arch_data: architecture specific data (optional, defaults | |
* to an empty struct) | |
* | |
* vdso_data will be accessed by 64 bit and compat code at the same time | |
* so we should be careful before modifying this structure. | |
* | |
* @basetime is used to store the base time for the system wide time getter | |
* VVAR page. | |
* | |
* @offset is used by the special time namespace VVAR pages which are | |
* installed instead of the real VVAR page. These namespace pages must set | |
* @seq to 1 and @clock_mode to VDSO_CLOCKMODE_TIMENS to force the code into | |
* the time namespace slow path. The namespace aware functions retrieve the | |
* real system wide VVAR page, read host time and add the per clock offset. | |
* For clocks which are not affected by time namespace adjustment the | |
* offset must be zero. | |
*/ | |
struct vdso_data { | |
__u32 seq; | |
__s32 clock_mode; | |
__u64 cycle_last; | |
__u64 mask; | |
__u32 mult; | |
__u32 shift; | |
union { | |
struct vdso_timestamp basetime[VDSO_BASES]; | |
//struct timens_offset offset[VDSO_BASES]; | |
}; | |
__s32 tz_minuteswest; | |
__s32 tz_dsttime; | |
__u32 hrtimer_res; | |
__u32 __unused; | |
struct arch_vdso_data arch_data; | |
}; | |
// main code | |
uintptr_t get_vvar_address() { | |
// quickly parse /proc/self/maps to find [vvar] mapping | |
char mmaps [4096*4]; | |
FILE* mmapsfile = fopen("/proc/self/maps", "r"); | |
if (!mmapsfile) { | |
fprintf(stderr, "could not access own maps\n"); | |
exit(1); | |
} | |
size_t nread = fread(mmaps, 1, sizeof(mmaps), mmapsfile); | |
assert(nread > 0 && nread < sizeof(mmaps)); | |
fclose(mmapsfile); | |
mmaps[nread] = 0; | |
for (char* line = mmaps; line != NULL;) { | |
char* next_line = strchr(line, '\n'); | |
if (next_line != NULL) *(next_line++) = 0; | |
if (strstr(line, "[vvar]")) | |
return strtol(line, NULL, 16); | |
line = next_line; | |
} | |
fprintf(stderr, "could not find [vvar] mapping\n"); | |
exit(1); | |
} | |
const char* CLOCKSOURCE_NAMES [CS_BASES] = { | |
"HRES_COARSE", | |
"RAW", | |
}; | |
const char* CLOCK_NAMES [VDSO_BASES] = { | |
"REALTIME", | |
"MONOTONIC", | |
"PROCESS_CPUTIME_ID", | |
"THREAD_CPUTIME_ID", | |
"MONOTONIC_RAW", | |
"REALTIME_COARSE", | |
"MONOTONIC_COARSE", | |
"BOOTTIME", | |
"REALTIME_ALARM", | |
"BOOTTIME_ALARM", | |
"SGI_CYCLE", | |
"TAI", | |
}; | |
void print_vdso_timestamp(const struct vdso_timestamp* vs) { | |
__u64 __time = vs->sec; | |
__u64 secs = __time % 60; __time /= 60; | |
__u64 mins = __time % 60; __time /= 60; | |
__u64 hours = __time % 24; __time /= 24; | |
__u64 days = __time; | |
printf("%5llu days, %2llu:%02llu:%02llu.%09llu", days, hours, mins, secs, vs->nsec); | |
} | |
int main() { | |
uintptr_t vvar_addr = get_vvar_address(); | |
printf("vvar address: %p\n", (void*)vvar_addr); | |
// printf("Entering strict seccomp...\n"); | |
// prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); | |
printf("\n\n1. dumping VVAR data...\n"); | |
const struct vdso_data *_vdso_data = (const struct vdso_data *)(vvar_addr + 128); | |
for (size_t i = 0; i < CS_BASES; i++) { | |
const struct vdso_data *vd = _vdso_data + i; | |
printf("\n_vdso_data[%s] = {\n", CLOCKSOURCE_NAMES[i]); | |
printf(" seq = %u\n", vd->seq); | |
printf("\n"); | |
printf(" clock_mode = %d\n", vd->clock_mode); | |
printf(" cycle_last = %llu\n", vd->cycle_last); | |
printf(" mask = %llu\n", vd->mask); | |
printf(" mult = %u\n", vd->mult); | |
printf(" shift = %u\n", vd->shift); | |
printf("\n"); | |
for (size_t i = 0; i < VDSO_BASES; i++) { | |
printf(" basetime[%s] = ", CLOCK_NAMES[i]); | |
print_vdso_timestamp(&vd->basetime[i]); | |
printf("\n"); | |
} | |
printf("\n"); | |
printf(" tz_minuteswest = %d\n", vd->tz_minuteswest); | |
printf(" tz_dsttime = %d\n", vd->tz_dsttime); | |
printf(" hrtimer_res = %u\n", vd->hrtimer_res); | |
printf(" __unused = %u\n", vd->__unused); | |
printf("}\n"); | |
} | |
printf("\n\n2. attempting to read TSC...\n\n"); | |
printf("TSC read correctly: %llu\n", __rdtsc()); | |
printf("\n\n3. attempting to call clock_gettime...\n\n"); | |
struct timespec ctime = {0, 0}; | |
if (!clock_gettime(CLOCK_REALTIME, &ctime)) { | |
print_vdso_timestamp((struct vdso_timestamp*)&ctime); | |
printf("\n"); | |
} else { | |
printf("could NOT read clock_gettime\n"); | |
} | |
// libc is not seccomp-friendly and turns exit() into exit_group() | |
syscall(SYS_exit, 0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was super helpful for debugging a clock issue! Thanks for writing it so I didn't have to :)