Strace With C Executable?

Viewed 464

I had multiple simple questions in my latest job interview.

At first, I was asked to write a simple program that takes an input x from the user and allocates (using malloc) x bytes in memory.

I simply wrote:

void main()
{
    int x;
    scanf("%d",&x);
    malloc(x);
}

Then I was told to show all the system calls being called when running my executable so I went to the terminal and types:

strace ./my_program.o

Which was great until he asked something like:

The output you received from running strace on your program was probably very messy. And there’s no way to tell which system call was used during the execution of malloc. Can you suggest a simple addition to your C code, such that you will be able to spot the system call used during the execution of malloc anyway. BTW, You’re not allowed to add flags to strace and Your change must be made in the C code.

I lost him here. what addition can be made to the C code?


An output example of your suggestions (still doesn't help as there is only one write for a strange reason not 2 as I changed my C code to have one before malloc and one exactly after)

execve("./a.out", ["./a.out"], 0x7ffc38701620 /* 50 vars */) = 0
brk(NULL)                               = 0x55df6cc1b000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=96020, ...}) = 0
mmap(NULL, 96020, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb9d4900000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb9d48fe000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb9d42fe000
mprotect(0x7fb9d44e5000, 2097152, PROT_NONE) = 0
mmap(0x7fb9d46e5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fb9d46e5000
mmap(0x7fb9d46eb000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb9d46eb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fb9d48ff500) = 0
mprotect(0x7fb9d46e5000, 16384, PROT_READ) = 0
mprotect(0x55df6b542000, 4096, PROT_READ) = 0
mprotect(0x7fb9d4918000, 4096, PROT_READ) = 0
munmap(0x7fb9d4900000, 96020)           = 0
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL)                               = 0x55df6cc1b000
brk(0x55df6cc3c000)                     = 0x55df6cc3c000
read(0, 
"\n", 1024)                     = 1
read(0, 5
"5\n", 1024)                    = 2
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "__________________________", 26__________________________) = 26
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)
exit_group(0)                           = ?
+++ exited with 0 +++
1 Answers

Your malloc does not issue any syscalls (e.g. brk) for small values of x (e.g. 23)

That is because the prior scanf call has to [internally] call malloc. The internal malloc calls brk.

The heap already has enough space allocated, so your malloc can fulfill the request without calling brk.

To be able to see this, put a usleep(1) before your malloc call and after. This generates innocuous calls to the nanosleep syscall which serve as markers around your malloc call.


Here is the strace output for an input value of 23:

execve("./fix1", ["./fix1"], 0x7ffea3366ac0 /* 94 vars */) = 0
brk(NULL)                               = 0x15fc000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcf4a51d40) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=340324, ...}) = 0
mmap(NULL, 340324, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f15ef9bf000
close(3)                                = 0
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 E\2\0\0\0\0\0"..., 832) = 832
lseek(3, 792, SEEK_SET)                 = 792
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0g\225=\371\27\203\227A\277:}7a\216\376\301"..., 68) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2786704, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f15ef9bd000
lseek(3, 792, SEEK_SET)                 = 792
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0g\225=\371\27\203\227A\277:}7a\216\376\301"..., 68) = 68
lseek(3, 864, SEEK_SET)                 = 864
read(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32) = 32
mmap(NULL, 1857472, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f15ef7f7000
mprotect(0x7f15ef819000, 1679360, PROT_NONE) = 0
mmap(0x7f15ef819000, 1363968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f15ef819000
mmap(0x7f15ef966000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16f000) = 0x7f15ef966000
mmap(0x7f15ef9b3000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bb000) = 0x7f15ef9b3000
mmap(0x7f15ef9b9000, 14272, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f15ef9b9000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f15ef9be500) = 0
mprotect(0x7f15ef9b3000, 16384, PROT_READ) = 0
mprotect(0x403000, 4096, PROT_READ)     = 0
mprotect(0x7f15efa3c000, 4096, PROT_READ) = 0
munmap(0x7f15ef9bf000, 340324)          = 0
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}) = 0
brk(NULL)                               = 0x15fc000
brk(0x161d000)                          = 0x161d000
brk(NULL)                               = 0x161d000
read(0, "23\n", 1024)                   = 3
nanosleep({tv_sec=0, tv_nsec=1000}, NULL) = 0
nanosleep({tv_sec=0, tv_nsec=1000}, NULL) = 0
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)
exit_group(0)                           = ?
+++ exited with 0 +++

However, a large value will cause your malloc to issue an mmap syscall.

Here is the the output for a value of 1000000000:

execve("./fix1", ["./fix1"], 0x7ffe4ec746d0 /* 94 vars */) = 0
brk(NULL)                               = 0x13df000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe5a768930) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=340324, ...}) = 0
mmap(NULL, 340324, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f61b6e83000
close(3)                                = 0
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 E\2\0\0\0\0\0"..., 832) = 832
lseek(3, 792, SEEK_SET)                 = 792
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0g\225=\371\27\203\227A\277:}7a\216\376\301"..., 68) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2786704, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f61b6e81000
lseek(3, 792, SEEK_SET)                 = 792
read(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0g\225=\371\27\203\227A\277:}7a\216\376\301"..., 68) = 68
lseek(3, 864, SEEK_SET)                 = 864
read(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32) = 32
mmap(NULL, 1857472, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f61b6cbb000
mprotect(0x7f61b6cdd000, 1679360, PROT_NONE) = 0
mmap(0x7f61b6cdd000, 1363968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f61b6cdd000
mmap(0x7f61b6e2a000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16f000) = 0x7f61b6e2a000
mmap(0x7f61b6e77000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bb000) = 0x7f61b6e77000
mmap(0x7f61b6e7d000, 14272, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f61b6e7d000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f61b6e82500) = 0
mprotect(0x7f61b6e77000, 16384, PROT_READ) = 0
mprotect(0x403000, 4096, PROT_READ)     = 0
mprotect(0x7f61b6f00000, 4096, PROT_READ) = 0
munmap(0x7f61b6e83000, 340324)          = 0
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}) = 0
brk(NULL)                               = 0x13df000
brk(0x1400000)                          = 0x1400000
brk(NULL)                               = 0x1400000
read(0, "1000000000\n", 1024)           = 11
nanosleep({tv_sec=0, tv_nsec=1000}, NULL) = 0
mmap(NULL, 1000001536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f617b30e000
nanosleep({tv_sec=0, tv_nsec=1000}, NULL) = 0
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)
exit_group(0)                           = ?
+++ exited with 0 +++

Here is the program I used to generate the above. Note that anything that generates a syscall that is not part of scanf or malloc could replace the usleep calls (e.g. time):

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

void
main()
{
    int x;

    scanf("%d", &x);
    usleep(1);
    malloc(x);
    usleep(1);
}

UPDATE:

Can you please explain why this won't work: [redacted for space] – ariel

Using write should be fine. It's "innocuous" and the write syscalls should show up just fine. As I mentioned, any syscall could be used [if it's uniquely distinguishable from anything that scanf or malloc would do].

and why you said "not part of scanf"? – ariel

Because [as I mentioned] scanf will do a malloc [and a free--but that probably won't generate a syscall].

Even with large numbers I don't see malloc being called. – ariel

I'm not sure what's going on in your setup. I used 1,000,000,000 to really force the issuance of a syscall by your malloc.

Either the nanosleep or your write syscalls should work as markers.

Do you see an mmap call in your trace [or more brk calls] between the marker calls?


UPDATE #2:

Plus, I can't get an input to make it show sbrk() how can I do that – ariel

It's up to the individual heap manager to decide which of the syscalls sbrk, brk, and/or mmap it will use and when.

On my system [linux], I'm using the malloc from glibc. My suspicion is that it only does brk and not sbrk

brk and sbrk are similar enough that a given manager will likely use one or the other but not both -- YMMV.

From man sbrk:

On Linux, sbrk() is implemented as a library function that uses the brk() system call, and does some internal bookkeeping so that it can return the old break value.

Related