2

I am trying to understand the implementation of time() in glibc: https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86/time.c.html#time

If you expand the macros (hovering over them), you get:

time_t time (time_t *t)
{
  unsigned long int resultvar;
  long int __arg1 = (long int) (t);
  register long int _a1 asm (""rdi"") = __arg1;
  asm volatile ( ""syscall\n\t"" : ""=a"" (resultvar) : ""0"" (201) , ""r"" (_a1) : ""memory"", ""cc"", ""r11"", ""cx"");
  (long int) resultvar;
}

The assembly command clearly must return the current time, but I can not understand how. Checking on Intel manual syscall I see it clobbers r11 and cx, the other clobbers are gcc stuff, up to there, I'm fine.

From what I read (https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html) "0" means something is used as both input and output, although I do not get what (201) means (I was expecting a variable name, not a number). Any ideas?

I am not sure if _a1 is required to have info, I would assume it can be NULL, so the only real input of the assembly command is that (201), but I can't tell how syscall together with 201 gives me access to the internal clock.

P.S. Bonus question: from what I read on the Intel manual I am under the impression that the only clock available is expected to be on the MoBo rather than be part of the CPU hardware. Have I misunderstood?

phuclv
  • 37,963
  • 15
  • 156
  • 475
Dirich
  • 412
  • 3
  • 13
  • 6
    `201` is the syscall number. System calls are identified by numbers. See for example [this table](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/). `_a1` can not be `NULL` since that is your output pointer (initialized from the `t` argument). – Jester Jul 03 '17 at 20:58
  • Actually it can be `NULL`, the result is also returned via the `rax` register (that's what the `=a` means). – Jester Jul 03 '17 at 21:04
  • Thanks @Jester. I've found 201 is sys_time, sys_time in private.h calls time(), the only other implementation of time I found calls __gettimeofday, the implementation of __gettimeofday fills the timespec structure by calling time(). I'm getting stuck in a loop... do you have an idea where is the code that actually reads the time from an hardware clock? – Dirich Jul 03 '17 at 21:21
  • Nevermind, it was the linux kernel time(): http://elixir.free-electrons.com/linux/v4.11.7/source/kernel/time/time.c#L63 – Dirich Jul 03 '17 at 21:28
  • You have `""` (two double quotes) everywhere there should only be `"`" (one double-quote). This wouldn't compile. Also, weird that they use a hard-coded `201` instead of `__NR_time` or `SYS_time` from `asm/unistd.h` or `sys/syscalls.h`. Oh, I guess you're looking at preprocessed source, with macros expanded. – Peter Cordes Jul 08 '23 at 16:10

2 Answers2

4

This complicated-looking inline assembly just causes the following assembly instructions to be emitted by the compiler:

mov     eax, 201
syscall

So, the entire time function is just:

time:
    mov     eax, 201
    syscall
    ret

The immediate value 201 (0xC9 in hexadecimal notation) is moved into the EAX register, and then the syscall instruction is executed. This instruction does just what the name suggests: it makes a system call. This is basically the way you call platform API functions on Linux. See also section A.2 ("AMD64 Linux Kernel Conventions") of the System V AMD64 ABI.

In brief:

  • The system call ID number is placed into rax.

    (In this case, the number is just 32 bits, so the assembly code places it into eax. The upper 32 bits are implicitly zeroed, saving some bytes in the size of the mov instruction.)

  • The arguments for the system call, if any, are placed in registers: rdi, rsi, rdx, r10, r8, and r9.

    (In this case, for system call #201, there are no arguments that need to be specified, so none of these registers are initialized by the time function.)

  • After syscall is invoked, its result is contained in rax. Conventionally, negative values (−4095 to −1) indicate an error, corresponding to −errno.

  • For system calls, the rcx and r11 registers are treated as volatile, which means that their contents are subject to being clobbered. If the caller cares about those values, it needs to preserve them. All other registers' values are saved across the system call.

    (This is why the clobbers are there in the extended inline asm syntax.)

There is a reference for 64-bit Linux system calls available here (32-bit Linux system calls are here). You can see that 201 (0xC9) corresponds to sys_time.

sys_time interprets the RDI register as a time_t* value. This code:

long int __arg1 = (long int) (t);
register long int _a1 asm ("rdi") = __arg1;

causes the function's parameter, t, to be stored in the RDI register. That doesn't actually cause any machine instructions to be generated, though, because the System V AMD64 calling convention already passes the first parameter of a function in RDI, so t is already in RDI.

The sys_time system call just fills the pointer it finds in RDI, which is the same as the time function's t argument. It also returns its result (an error code) in RAX, which is always used for the return value of a function under the System V AMD64 calling convention, so no machine instructions are required there, either.

Perhaps more clearly:

# inputs:  RDI is a pointer to time_t that will be filled in
# returns: result is left in RAX
time:
    mov     eax, 201
    syscall
    ret
Community
  • 1
  • 1
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
0

Regarding your PS: All current Intel CPUs have their own clocks which can be read using instructions such as rdtsc. However, whether they can be used as real-time clocks depends on the configuration of the execution environment (e.g., kernel/hypervisor support is needed, some power saving states may need to be disabled, and so on).

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
  • Thanks but rdtsc is not a clock (as in "it gives the hour", an "absolute time"), it is a counter of ticks which can be used to measure the passing of time (a "relative time"), with all the caveat of the varying frequency on (very) old architectures. I trying to understand how the "absolute time" is obtained in the current glibc, which turns out defers everything to the linux kernel. – Dirich Jul 18 '17 at 18:32
  • Yes, glibc uses the kernel, but it can also leverage the vDSO implementation of `time`, `gettimeofday` and `clock_gettime`, which avoids the need for a system call (if one of the CPU clocks can indeed be used). – Florian Weimer Jul 18 '17 at 18:35
  • 1
    And how to access those clocks is what I was looking for. I tracked down the timekeeper struct, and I know about ntp, but I can't find the code (most likely assembly) that reads the "absolute clock". To be honest I am still unsure if it is a CPU clock or the MoBo's. – Dirich Jul 19 '17 at 21:06