Last active
May 21, 2023 14:44
-
-
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
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
/** | |
* 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