How to print exact value of the program counter in C

19,376

Solution 1

You should be able to determine the PC by using the __current_pc() intrinsic in the ARM compiler toolchain (the ARM compiler supports many of the same extensions as GCC).* This is particular to ARM:

int main () {
    printf("%#x\n", __current_pc());
    printf("%#x\n", __current_pc());
    printf("%#x\n", __current_pc());
    return 0;
}

* Thanks to FrankH. for pointing out the presence of __current_pc()

In general, the PC gets saved as the return address in a function call. On non-ARM systems with GCC, you can call __builtin_return_address(0) to obtain the return address of the current function call context. Obtaining the program counter in this way incurs the penalty of adding a function call, but it avoids inline assembly, so this technique is portable to any system supported by GCC.

__attribute__ ((__noinline__))
void * get_pc () { return __builtin_return_address(0); }

int main () {
    printf("%p\n", get_pc());
    printf("%p\n", get_pc());
    printf("%p\n", get_pc());
    return 0;
}

When I run the above program on my x86 system, it produces the output:

0x8048432
0x8048447
0x804845c

When disassembled in gdb:

Dump of assembler code for function main:
   0x08048424 <+0>: push   %ebp
   0x08048425 <+1>: mov    %esp,%ebp
   0x08048427 <+3>: and    $0xfffffff0,%esp
   0x0804842a <+6>: sub    $0x10,%esp
   0x0804842d <+9>: call   0x804841c <get_pc>
   0x08048432 <+14>:    mov    %eax,0x4(%esp)
   0x08048436 <+18>:    movl   $0x8048510,(%esp)
   0x0804843d <+25>:    call   0x80482f0 <printf@plt>
   0x08048442 <+30>:    call   0x804841c <get_pc>
   0x08048447 <+35>:    mov    %eax,0x4(%esp)
   0x0804844b <+39>:    movl   $0x8048510,(%esp)
   0x08048452 <+46>:    call   0x80482f0 <printf@plt>
   0x08048457 <+51>:    call   0x804841c <get_pc>
   0x0804845c <+56>:    mov    %eax,0x4(%esp)
   0x08048460 <+60>:    movl   $0x8048510,(%esp)
   0x08048467 <+67>:    call   0x80482f0 <printf@plt>
   0x0804846c <+72>:    mov    $0x0,%eax
   0x08048471 <+77>:    leave  
   0x08048472 <+78>:    ret    
End of assembler dump.

Solution 2

On ARM, you can use:

static __inline__ void * get_pc(void)  {
    void *pc;
    asm("mov %0, pc" : "=r"(pc));
    return pc;
}

Or this one should work as well:

static __inline__ void * get_pc(void) {
    register void * pc __asm__("pc");
    __asm__("" : "=r"(pc));
    return pc;
}

The forced inlining is important here, because that ensures you retrieve PC as per the call site.

Edit: just remembered, __current_pc() ARM intrinsic. GCC should have this as well.

Solution 3

Well I think you can get the information by inserting assembly blocks inside your C code. This will totally depend on your compiler and the register set of your platform. I did it like this:

int get_counter1()

{

    __asm__ ("lea (%rip), %eax ") ;
}

int get_counter2()

{

    int x = 0;
    __asm__ ("lea (%rip), %eax") ;
}

int main()

{

    printf("%x\n",get_counter1());
    printf("%x\n",get_counter2());
    return 0;
}

4004ce

4004e1

Share:
19,376
manav m-n
Author by

manav m-n

Please note that this profile is outdated and kept here only for historical reasons. Name: Unknown Location: Unknown Education: Unknown Facebook/Twitter/Instagram: None

Updated on July 22, 2022

Comments

  • manav m-n
    manav m-n almost 2 years

    I want to write a C program which would print the contents of the program counter PC. Can this be done from user space, or assembly, or some specific kernel routines are used?

  • FrankH.
    FrankH. over 10 years
    return address and PC (EIP / RIP) are not the same thing.
  • FrankH.
    FrankH. over 10 years
    apologies, in ARM I should say, return address (LR) and PC are not the same thing.
  • FrankH.
    FrankH. over 10 years
    And more ... the reason why yours works is because you force a function call (which makes LR within your func the PC of the call site). This is unnecessarily inefficient.
  • jxh
    jxh over 10 years
    @FrankH.: Thanks for the input. I have adjusted my answer.
  • rsaxvc
    rsaxvc almost 10 years
    ARM's compilers <= 5 are not variants of GCC. ARM's compiler 6 is a variant of LLVM.
  • jxh
    jxh almost 10 years
    @rsaxvc: Thanks, clarified the answer.
  • Andre Holzner
    Andre Holzner over 4 years
    on aarch64 the following seems to work (in your first function): asm("adr %0, ." : "=r"(pc));.