C/C++ function definitions without assembly

前端 未结 6 1857
野趣味
野趣味 2020-11-28 01:38

I always thought that functions like printf() are, in the last step, defined using inline assembly. That deep in the bowels of stdio.h is buried some asm code t

6条回答
  •  甜味超标
    2020-11-28 02:31

    In Linux, strace utility allows you to see what system calls are made by a program. So, taking a program like this

    
        int main(){
        printf("x");
        return 0;
        }
    
    

    Say, you compile it as printx, then strace printx gives

    
        execve("./printx", ["./printx"], [/* 49 vars */]) = 0
        brk(0)                                  = 0xb66000
        access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
        mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0e5000
        access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
        open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
        fstat(3, {st_mode=S_IFREG|0644, st_size=119796, ...}) = 0
        mmap(NULL, 119796, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa6dc0c7000
        close(3)                                = 0
        access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
        open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
        read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832
        fstat(3, {st_mode=S_IFREG|0755, st_size=1811128, ...}) = 0
        mmap(NULL, 3925208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa6dbb06000
        mprotect(0x7fa6dbcbb000, 2093056, PROT_NONE) = 0
        mmap(0x7fa6dbeba000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b4000) = 0x7fa6dbeba000
        mmap(0x7fa6dbec0000, 17624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa6dbec0000
        close(3)                                = 0
        mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c6000
        mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c5000
        mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c4000
        arch_prctl(ARCH_SET_FS, 0x7fa6dc0c5700) = 0
        mprotect(0x7fa6dbeba000, 16384, PROT_READ) = 0
        mprotect(0x600000, 4096, PROT_READ)     = 0
        mprotect(0x7fa6dc0e7000, 4096, PROT_READ) = 0
        munmap(0x7fa6dc0c7000, 119796)          = 0
        fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
        mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0e4000
        write(1, "x", 1x)                        = 1
        exit_group(0)                           = ?
    
    

    The rubber meets the road (sort off, see below) in the next to last call of the trace: write(1,"x",1x). At this point the control passes from user-land printx to the Linux kernel which handles the rest. write() is a wrapper function declared in unistd.h

    
        extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;
    
    

    Most system calls are wrapped in this way. The wrapper function, as its name suggests, is little more than a thin code layer that places the arguments in the correct registers and then executes a software interrupt 0x80. The kernel traps the interrupt and the rest is history. Or at least that's the way it used to work. Apparently, the overhead of interrupt trapping was quite high and, as an earlier post pointed out, modern CPU architectures introduced sysenter assembly instruction, which accomplishes the same result at speed. This page System Calls has quite a nice summary of how system calls work.

    I feel that you will probably be a bit disappointed with this answer, as was I. Clearly, in some sense, this is a false bottom as there are still quite a few things that have to happen between the call to write() and the point at which the graphics card frame buffer is actually modified to make the letter "x" appear on your screen. Zooming in on the point of contact (to stay with the "rubber against the road" analogy) by diving into the kernel is sure to be educational if a time consuming endeavor. I am guessing you would have to travel through several layers of abstraction like buffered output streams, character devices, etc. Be sure to post the results should you decide to follow up on this:)

提交回复
热议问题