Skip to content

Instantly share code, notes, and snippets.

@shenghaoyang
Last active May 21, 2023 14:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shenghaoyang/ae26b84a44e74be71a55e440de4ed3a3 to your computer and use it in GitHub Desktop.
Save shenghaoyang/ae26b84a44e74be71a55e440de4ed3a3 to your computer and use it in GitHub Desktop.
hacky code to get tsc -> ns conversion factors from the kernel - see https://github.com/shenghaoyang/get_tsc_coeff instead
/**
* Reads the TSC conversion factors from the kernel, used to convert
* TSC ticks into nanoseconds with respect to the frequency \c CLOCK_MONOTONIC
* is running at.
*
* Works on Kernel 6.3.3, x86_64, without time namespacing.
*
* Absolutely no guarantees that the values provided are accurate on other
* kernel versions - it depends heavily on private implementation details.
*
* BSD Zero Clause License
*
* Copyright (c) 2023 Shenghao Yang
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#ifndef __x86_64__
#error only works on x86_64
#endif
#define barrier() __asm__ __volatile__("": : :"memory")
#define VDSO_VVAR_OFFSET (UINT64_C(128))
#define VDSO_CLOCKMODE_TSC (1)
/**
* First few fields of the \c vdso_data_hdr structure.
*/
struct vdso_data_hdr {
uint32_t seq;
int32_t clock_mode;
uint64_t cycle_last;
uint64_t mask;
uint32_t mult;
uint32_t shift;
// Remaining fields omitted.
};
/**
* Obtain the start address of the \c vvar data page mapped into the process'
* address space by the kernel.
*
* \retval NULL if the start address could not be determined.
*/
void* get_vvar_start() {
void* out = NULL;
size_t linebuf_sz = 4096;
char *linebuf = malloc(linebuf_sz);
if (!linebuf)
goto alloc_fail;
FILE *const smaps = fopen("/proc/self/smaps", "r");
if (!smaps)
goto fopen_fail;
for (ssize_t ret = 0; (ret = getline(&linebuf, &linebuf_sz, smaps)) != -1;) {
if (!strstr(linebuf, "[vvar]\n"))
continue;
errno = 0;
char *endptr;
uintmax_t vvar_start = strtoumax(linebuf, &endptr, 16);
if ((errno != 0) || (endptr == linebuf))
break;
out = (void *)((uintptr_t)vvar_start);
break;
}
fclose(smaps);
fopen_fail:
free(linebuf);
alloc_fail:
return out;
}
/**
* Read the header of the first \c vdso_data structure located in the \c [vvar]
* data page.
*
* \param out where to write the header to.
* \param vvar_start
*/
void read_vdso_data(struct vdso_data_hdr *out, void* vvar_start) {
const volatile struct vdso_data_hdr *mapped = (void *)((uintptr_t)vvar_start + (uintptr_t)VDSO_VVAR_OFFSET);
uint32_t seq, seq2;
do {
seq = mapped->seq;
// Do we even need this
// Does volatile even work well on x86_64?
barrier();
out->seq = seq;
out->clock_mode = mapped->clock_mode;
out->cycle_last = mapped->cycle_last;
out->mask = mapped->mask;
out->mult = mapped->mult;
out->shift = mapped->shift;
seq2 = mapped->seq;
} while (seq != seq2);
}
/**
* Obtain the \c mult and \c shift constants from the vDSO data page.
*
* \return non-zero if TSC is not used for timekeeping.
* \return zero on success.
*/
int get_tsc_mult_shift(uint32_t *mult, uint32_t *shift, const struct vdso_data_hdr *read) {
if (read->clock_mode != VDSO_CLOCKMODE_TSC) {
return 1;
}
*mult = read->mult;
*shift = read->shift;
return 0;
}
int main(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
void *vvar_start = get_vvar_start();
if (!vvar_start) {
fputs("fatal: unable to obtain [vvar] start address\n", stderr);
return 1;
}
struct vdso_data_hdr hdr;
read_vdso_data(&hdr, vvar_start);
uint32_t mult, shift;
if (get_tsc_mult_shift(&mult, &shift, &hdr)) {
fputs("fatal: (clocksource != TSC) || (time_ns != initial)\n", stderr);
return 1;
}
printf("ns = (tsc * %"PRIu32") >> %"PRIu32"\n", mult, shift);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment