2

I wrote a short program in c++ :

#include<iostream>
using namespace std;

    int main(){
    int x=10;
    int y=20;
    cout<< x+y <<endl;
    return 0;
    }

just out of curiosity i wanted to understand a program behind the hood so i was playing with gdb & came acrooss info registers command .when i use info registers in gdb i get output like this:

(gdb) info registers
rax            0x400756 4196182
rbx            0x0  0
rcx            0x6  6
rdx            0x7fffffffd418   140737488344088
rsi            0x7fffffffd408   140737488344072
rdi            0x1  1
rbp            0x7fffffffd320   0x7fffffffd320
rsp            0x7fffffffd320   0x7fffffffd320
r8             0x7ffff7ac1e80   140737348640384
r9             0x7ffff7dcfea0   140737351843488
r10            0x7fffffffd080   140737488343168
r11            0x7ffff773a410   140737344939024
r12            0x400660 4195936
r13            0x7fffffffd400   140737488344064
r14            0x0  0
r15            0x0  0
rip            0x40075a 0x40075a <main+4>
eflags         0x246    [ PF ZF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

I understand these are registers and their values but what I want to know is how/why are registers associated with a process. the values of registers should be changing continuously as different processes are scheduled by the operating system? I referred to the command info registers & this is what I found but this is still confusing.

info registers -> Prints the names and values of all registers except floating-point and vector registers (in the selected stack frame).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
anekix
  • 2,393
  • 2
  • 30
  • 57
  • 4
    This is less a question about assembly and debuggers, but more about how operating systems and multi-tasking works. In short, the operating system keeps *copies* of all registers. One copy per thread of execution. – Some programmer dude Feb 14 '18 at 11:13
  • @Someprogrammerdude what tags can i use then? – anekix Feb 14 '18 at 11:13
  • also as far as i know there are a fixed set of registers associated with a CPU. – anekix Feb 14 '18 at 11:15
  • 1
    @anekix Google search *context switch*. – llllllllll Feb 14 '18 at 11:18
  • @liliscent i know context switching thats why i was getting confused becuase if i run `info registers` multiple times i get the same values but these should change because of context switching – anekix Feb 14 '18 at 11:19
  • 4
    @anekix You need to read the details of context switch, not just a name. Preserving register values is one of the most basic steps of context switch. – llllllllll Feb 14 '18 at 11:21
  • @liliscent i think i got it now. context switch stores the state of these regsters & when i run multiple times since the gdb is attached to this particular program it shows same values :). is this correct? – anekix Feb 14 '18 at 11:21
  • 1
    @anekix Correct. – llllllllll Feb 14 '18 at 11:24
  • 1
    Read a book on how computers work. This is way out of scope for SO. – Lightness Races in Orbit Feb 14 '18 at 11:43
  • @LightnessRacesinOrbit Thanks will do :) – anekix Feb 14 '18 at 11:58
  • 2
    The debugger shows registers state for the particular debugged thread, not actual values of CPU registers of the debugger implementation, or other tasks, so if you have breakpointed the debugged code, the registers state should not change at all (but it is displaying the saved registers state of that breakpointed thread, not physical CPU registers, which are being used by the debugger itself just to produce that output, so they contain completely unrelated values). – Ped7g Feb 14 '18 at 12:01

2 Answers2

5

Registers change all the time. In fact, even the debugger changes register values, as it has to run itself.

However, while you look at your program with a debugger, the debugger suspends your running process. As part of suspending, the CPU state is saved to RAM. The debugger understands this, and can just look at the suspended state in RAM. Say that register R1 was saved to address 0x1234 on suspending, then the debugger can just print the bytes stored at that address.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • This is a vast simplification. The OS saves registers for every process when it's not running, and provides an API for reading/writing the saved register state, and memory, of other processes. In Linux, this API is called `ptrace`; it's the system call GDB uses to read register values and to single-step. But yes, using `ptrace`, GDB reads saved register values of the target process *from memory* via the kernel. `ptrace` is also what `strace` uses to trace system calls. [Playing with ptrace, Part I](http://www.linuxjournal.com/article/6100). – Peter Cordes Feb 14 '18 at 23:27
  • 1
    Simplifying it is, I'm sure, deliberate. MSalters said everything that you just did, except to name specific technical terms that aren't relevant or necessary to this question at all. – Lightness Races in Orbit Feb 14 '18 at 23:54
4

Each thread/process has its own register values. The user-space "architectural state" (register values) is saved on entering the kernel via a system call or interrupt. (This is true on all OSes).

See What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? for a look at Linux's system-call entry points, with the hand-written asm that actually saves registers on the process's kernel stack. (Each thread has its own kernel stack, in Linux).

In multi-tasking OSes in general, every process/thread has its own memory space for saving state, so context switches work by restoring the saved state from the thread being switched to. This is a bit of a simplification, because there's kernel state vs. saved user-space. state1


So any time a process isn't actually running on a CPU core, its register values are saved in memory.

The OS provides an API for reading/writing the saved register state, and memory, of other processes.

In Linux, this API is the ptrace(2) system call; it's what GDB uses to read register values and to single-step. Thus, GDB reads saved register values of the target process from memory, indirectly via the kernel. GDB's own code doesn't use any special x86 instructions, or even load / store from any special addresses; it just makes system calls because access to another process's state has to go through the kernel. (Well I think a process could map another process's memory into its own address space, if Linux even has a system call for that, but I think memory reads/writes actually go through ptrace just like register accesses.)

(I think) If the target process was currently executing (instead of suspended) when another process made a ptrace system call that read or wrote one of its register values, the kernel would have to interrupt it so its current state would be saved to memory. This doesn't normally happen with GDB: it only tries to read register values when it's suspended the target process.


ptrace is also what strace uses to trace system calls. See Playing with ptrace, Part I from Linux Journal. strace ./my_program is fantastically useful for systems programming, especially when making system calls from hand-written asm, to decode the args you're actually passing, and the return values.


Footnotes:

  1. In Linux, the actual switch to a new thread happens inside the kernel, from kernel context to kernel context. This saves "only" the integer registers on the kernel stack, sets rsp to the right place in the other thread's kernel stack, then restores the saved registers. So there's a function call that, when it returns, is executing in kernel mode for the new thread, with per-CPU kernel variables set appropriately. User-space state for the new thread is eventually restored the same way it would have been if the system call or interrupt that originally entered the kernel from user-space had returned without calling the scheduler. i.e. from the state saved by the system call or interrupt kernel entry point. Lazy / eager FPU state saving is another complication; the kernel generally avoids touching the FPU so it can avoid saving/restoring FPU state when just entering the kernel and returning back to the same user-space process.
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847