5

I have a simple debugger (using ptrace : http://pastebin.com/D0um3bUi) to count the number of instructions executed for a given input executable program. It uses ptrace single step execution mode to count instructions.

For that when the program 1)'s executable (a.out from gcc main.c) is given as input to my test debuggger it prints around 100k as instructions executed. When I use -static option it gives 10681 instructions.

Now in 2) I create an assembly program and use NASM for compiling and linking and then when this executable is given as test debuggers input it is showing 8 instructions as the count and which is apt.

The number of instructions executed in program 1) is high because of linking the program with system library's at runtime ? used -static and which reduces the count by a factor of 1/10. How can I ensure that the instruction count is only that of the main function in Program 1) and which is how Program 2) is reporting for the debugger?

1)

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    return 0;
}    

I use gcc to create the executable.

2)

; 64-bit "Hello World!" in Linux NASM

global _start            ; global entry point export for ld

section .text
_start:

    ; sys_write(stdout, message, length)

    mov    rax, 1        ; sys_write
    mov    rdi, 1        ; stdout
    mov    rsi, message    ; message address
    mov    rdx, length    ; message string length
    syscall

    ; sys_exit(return_code)

    mov    rax, 60        ; sys_exit
    mov    rdi, 0        ; return 0 (success)
    syscall

section .data
    message: db 'Hello, world!',0x0A    ; message and newline
    length:    equ    $-message        ; NASM definition pseudo-                             

I build with:

nasm -f elf64 -o main.o -s main.asm  
ld -o main main.o
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
crackerplace
  • 5,305
  • 8
  • 34
  • 42
  • 2
    Why don't you do a fair comparison, call `write()` instead of `printf()` in your C-program. Also, you can actually see some of the code that runs before `main()` if you disassemble the executable (e.g. `objdump -d [executable]`). – EOF Feb 04 '16 at 18:34
  • Yes.Sure,that's a finer point. – crackerplace Feb 04 '16 at 18:53
  • Not sure why would someone want to close this. – crackerplace Feb 04 '16 at 21:47
  • Why are there so many instructions (thousands) for such a simple 5 lines hello world program? – Matt Sep 03 '17 at 04:38

2 Answers2

6

The number of instructions executed in program 1) is high because of linking the program with system library's at runtime?

Yep, dynamic linking plus CRT (C runtime) startup files.

used -static and which reduces the count by a factor of 1/10.

So that just left the CRT start files, which do stuff before calling main, and after.

How can I ensure that the instruction count is only that of the main function in Program 1)`

Measure an empty main, then subtract that number from future measurements.

Unless your instruction-counters is smarter, and looks at symbols in the executable for the process it's tracing, it won't be able to tell which code came from where.

and which is how Program 2) is reporting for the debugger.

That's because there is no other code in that program. It's not that you somehow helped the debugger ignore some instructions, it's that you made a program without any instructions you didn't put there yourself.

If you want to see what actually happens when you run the gcc output, gdb a.out, b _start, r, and single-step. Once you get deep in the call tree, you're prob. going to want to use fin to finish execution of the current function, since you don't want to single-step through literally 1 million instructions, or even 10k.


related: How do I determine the number of x86 machine instructions executed in a C program? shows perf stat will count 3 user-space instructions total in a NASM program that does mov eax, 231 / syscall, linked into a static executable.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • The other point is compiling Program 1) to an assembly program and then linking it with GCC also did not reduce the instruction count which ideally shud have reduced because it is same like Program 2) – crackerplace Feb 04 '16 at 20:52
  • @whokares: lolwut? If you could get more efficient programs by explicitly using `gcc -O3 -S` and then assembling/linking that, gcc would already do so. gcc already internally makes asm output which it feeds to `as`. It's the contents of the asm source that matter, not the fact that it *is* asm. There's a SO question about this somewhere, which you should search for if that didn't make sense. – Peter Cordes Feb 04 '16 at 21:31
  • hmm.Got what u said as that makes no sense as internally gcc has steps from preprocessing to asm to executable etc.Somehow the source of .s was little similar to prog 2).Anywayz will re check. – crackerplace Feb 04 '16 at 21:40
6

Peter gave a very good answer, and I'm going to followup with a response that is cringe worthy and might garner some down votes. When linking directly with LD or indirectly with GCC, the default entry point for ELF executables is the label _start.

Your NASM code uses a global label _start so when your program is run the first code in your program will be the instructions of _start. When using GCC your program's typical entry point is the function main. What is hidden from you is that your C program also has a _start label but it is supplied by the C runtime startup objects.

The question now is - is there a way to bypass the C startup files so that the startup code can be avoided? Technically yes, but this is perilous territory that could yield undefined behaviour. If you are adventurous you can actually tell GCC to change the entry point of your program with the -e command line option. Rather than _start we could make our entry point main bypassing the C startup code. Since we are bypassing the C startup code we can also dispense with linking in the C runtime startup code with the -nostartfiles option.

You could use this command line to compile your C program:

gcc test.c -e main -nostartfiles

Unfortunately, there is a bit of a gotchya that has to be fixed in the C code. Normally when using the C runtime startup objects, after the environment is initialized a CALL is made to main. Normally main does a RET instruction which returns back to the C runtime code. At that point the C runtime gracefully exits your program. RET doesn't have anywhere to return when the -nostartfiles option is used, so it will likely segfault. To get around that we can call the C library _exit function to exit our program.

#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    _exit(0);  /* We exit application here, never reaching the return */

    return 0;
}   

Unless you omit frame pointers there are a few extra instructions emitted by GCC to setup the stack frame and tear it down, but the overhead is minimal.

Special Note

The process above doesn't seem to work for static builds (-static option in GCC) with standard glibc C library. This is discussed in this Stackoverflow answer. The dynamic version works because a shared object can register a function that gets called by the dynamic loader to perform initialization. When building statically this is generally done by the C runtime, but we've skipped that initialization. Because of that GLIBC functions like printf can fail. There are replacement C libraries that are standards compliant that can operate without C runtime initialization. One such product is MUSL.

Installing MUSL as an alternative to GLIBC

On Ubuntu 64-bit these commands should build and install the 64-bit version of MUSL:

git clone git://git.musl-libc.org/musl
cd musl
./configure --prefix=/usr/local/musl/x86-64
make
sudo make install

You can then use the MUSL wrapper for GCC to work with the MUSL's C library instead of the default GLIBC library on most Linux distributions. Parameters are just like GCC so you should be able to do:

/usr/local/musl/x86-64/bin/musl-gcc -e main -static -nostartfiles test.c

When running ./a.out generated with GLIBC it would likely segfault. MUSL doesn't need initialization prior to using most of the C library functions, so it should work even with the -static GCC option.


A fairer comparison

One of the issues with your comparison is that you call the SYS_WRITE system call directly in NASM, in C you are using printf. User EOF correctly commented that you might want to make it a fairer comparison by calling the write function in C instead of printf. write has far less overhead to it. You could amend your code to be:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    char *str = "Hello, world\n";
    write (STDOUT_FILENO, str, 13);
    _exit(0);
    return 0;
}

This will have more overhead than NASM's direct SYS_WRITE syscall, but far less than what printf would generate.


I'm going to issue the caveat that such code and trickery would likely not be taken well in a code review except for some fringe cases of software development.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • Thanks a lot.I really liked it.So does it mean,when GCC is used ,main is invoked after _start(which is injected) and before main C runtime linking happens.Also btw were you able to run the program,I had some issue as it did not terminate. – crackerplace Feb 04 '16 at 20:16
  • @whokares Generally with ELF the _C_ runtime will have a `_start` label that either directly or indirectly calls `main` as its last task. As for not exiting, what do you mean? Did it segfault? Did you add the `exit(0)` to the code (before the `return 0`)? – Michael Petch Feb 04 '16 at 20:27
  • Yes.Its the same error inspite of adding exit(0).Segmentation fault (core dumped) – crackerplace Feb 04 '16 at 20:32
  • Exactly.Before return. – crackerplace Feb 04 '16 at 20:33
  • Perilous :-).Used _exit but the issue persists. – crackerplace Feb 04 '16 at 20:45
  • Linux 3.13.0-65-generic #105-Ubuntu SMP x86_64 GNU/Linux – crackerplace Feb 04 '16 at 20:47
  • 14.04 ubuntu version – crackerplace Feb 04 '16 at 20:51
  • Why would anybody downvote this? After all, you did declare `main` properly, which is all most downvoters care about. Apart from that, posts like this are the reason I read SO. Please don't forget to post the resolution of your chat. – Peter - Reinstate Monica Feb 04 '16 at 21:00
  • @PeterA.Schneider : I've encountered some puritans on SO who don't take kindly to promoting potentially bad programming practices. It is the main reason why I put caveats all over this post. ;-) – Michael Petch Feb 04 '16 at 21:06
  • You should be able to get gcc to emit a "direct" invocation of sys_write, using the `_syscall3` macro to define an inline function that calls it. (see `_syscall(2)`.) Then you can compile with `gcc -nostdlib`, not just `-nostartfiles`, and get asm that's similar to the hand-written `_start`. Also, why not just write `_start(void)` instead of falsely calling it `_main`? – Peter Cordes Feb 05 '16 at 02:14
  • @PeterCordes I considered giving just an inline version, but it is unclear to me what the OP was originally trying to compare. You could rename it to *_start*, but it was just as easy to keep it as is (-e main is fine for tetsing this code). If you add `-nostdlib` then the OP won't be linking in the _C_ library, and again it is unclear if he wants to the _C_ library present. I assumed yes which allows him to test `printf` if he desires, so my answer was written that way. – Michael Petch Feb 05 '16 at 02:19
  • @PeterCordes : I even just considered giving him an equivalent GNU assembler file translated from his NASM code, and showing how to compile and link assembly files directly with _GCC_ but that answer can be found elsewhere on SO. – Michael Petch Feb 05 '16 at 02:23
  • BTW, `_syscall(2)` hasn't been updated since 2007 (on my Ubuntu system), and the `_syscallX` macros don't exist anywhere under `/usr/include`. Also, I missed the `-e main`; I was wondering if the linker was just defaulting to the start of the text section as the entry point, like when I run `ld` manually on (an object assembled from) some asm that doesn't define `_start` as a global symbol. Your post is interesting regardless of not trying to answer the direct question; I hadn't given much thought to getting gcc to make a nearly-bare executable from C source. – Peter Cordes Feb 05 '16 at 02:26
  • 1
    @PeterCordes : I should also point out that I wasn't trying to actually answer the original question. Your answer was just fine for that (thus my prologue). I upvoted your answer. My intent was just to deal with the case where someone might contemplate skipping C runtime initialization and potentially try to use `-static`. I could only find partial solutions on SO, so thought hell I have some brain cells to kill ;-) – Michael Petch Feb 05 '16 at 02:26
  • @PeterCordes If there is no *_startup* symbol present (or it isn't overridden with `-e`) the linker will default to the start of the text section. That applies to ELF/ELF64, but may be different with other executable formats. All this doesn't apply to Win32/Win64. I may have missed saying it but this is all ELF related and may not apply to other types of executable images. – Michael Petch Feb 05 '16 at 02:31
  • 1
    Thanks.It worked with musl and also the instruction count reduced by a big factor and is around 300 and which sounds fine.Also got this link which expands on assembly http://cs.lmu.edu/~ray/notes/nasmtutorial/ – crackerplace Feb 05 '16 at 10:05
  • 1
    @whokares : Just discovered your comment. Glad you tried out MUSL for experimentation. – Michael Petch Feb 17 '16 at 20:14