Why are there not any execve calls in my dtruss trace? - macos

I have a script like this:
script.sh
#!/bin/bash
clang -v
If I do a dtruss on it then I would expect to see an execve call to clang.
$ sudo dtruss -f -a -e ./script.sh
However, the trace does not contain an execve. Instead there is an error:
...
1703/0x16931: 856 4 0 sigaction(0x15, 0x7FFEE882A3B8, 0x7FFEE882A3F8) = 0 0
1703/0x16931: 858 4 0 sigaction(0x16, 0x7FFEE882A3C8, 0x7FFEE882A408) = 0 0
1703/0x16931: 874 4 0 sigaction(0x2, 0x7FFEE882A3C8, 0x7FFEE882A408) = 0 0
1703/0x16931: 881 4 0 sigaction(0x3, 0x7FFEE882A3C8, 0x7FFEE882A408) = 0 0
1703/0x16931: 883 4 0 sigaction(0x14, 0x7FFEE882A3C8, 0x7FFEE882A408) = 0 0
dtrace: error on enabled probe ID 2149 (ID 280: syscall::execve:return): invalid address (0x7fc2b5502c30) in action #12 at DIF offset 12
1703/0x16932: 2873: 0: 0 fork() = 0 0
1703/0x16932: 2879 138 5 thread_selfid(0x0, 0x0, 0x0) = 92466 0
1703/0x16932: 2958 8 0 issetugid(0x0, 0x0, 0x0) = 0 0
1703/0x16932: 2975 8 1 csrctl(0x0, 0x7FFEEE21DC3C, 0x4) = 0 0
1703/0x16932: 2985 12 6 csops(0x0, 0x0, 0x7FFEEE21E550) = 0 0
1703/0x16932: 3100 13 3 shared_region_check_np(0x7FFEEE21DA98, 0x0, 0x0)
...
What is causing this error?
How can I get the execve command to show so that I can see the program called and its arguments?

This means that the DTrace script that dtruss is using internally is accessing an invalid memory address, which is happening while it's trying to trace the execve call you're curious about. So basically, dtruss (or possibly DTrace itself) appears to have a bug which is preventing you from getting the information you want. Apple hasn't been the best about keeping DTrace and the tools that depend on it working well on macOS, unfortunately :-/.
For Bash / shell scripts in particular, you can make it print every command that it runs by adding set -x at the top of your script (more info in this other answer).
If you want, you could also try using DTrace directly instead -- this is a pretty simple one-liner (haven't tried running this myself so apologies if there are typos):
sudo dtrace -n 'proc:::exec-success /ppid == $target/ { trace(curpsinfo->pr_psargs); }' -c './script.sh'
The way this works is:
proc:::exec-success: Trace all exec-success events in the system, which fire in the subprocess when an exec*-family syscall returns successfully.
/ppid == $target/: Filter which means this only fires when the parent process's PID (ppid) matches the PID returned for the process started by the -c option we passed to the dtrace command ($target).
{ trace(curpsinfo->pr_psargs); }: This is the action to take when the event fires and it matches our filter. We simply print (trace) the arguments passed to the process, which is stored in the curpsinfo variable.
(If that fails with a similar-looking error, it's likely that the bug is in macOS's implementation of curpsinfo somewhere.)

Related

Tracing system calls using dtrace

I am running an application that runs with a process Id 423.
Basically want to debug this process.
The problem is that,
using the command sudo dtruss -a -t open_nocancel -p 423 I dont see print messages executed and also systems signals like sudo kill -30 423 dont seem to show in the stack trace. Am I missing something?. How do I achieve this?.
Sample Stack trace below
PID/THRD RELATIVE ELAPSD CPU SYSCALL(args) = return
423/0xcf5: 109498638 14 9 open_nocancel("/Users/krishna/.rstudio-desktop/sdb/s-3F25A09C/373AE888\0", 0x0, 0x1B6) = 21 0
423/0xcf5: 109509540 20 16 open_nocancel("/Users/krishna/.rstudio-desktop/history_database\0", 0x209, 0x1B6) = 20 0
423/0xcf5: 109510342 56 44 open_nocancel(".\0", 0x0, 0x1) = 20 0
423/0xcf5: 109516113 19 15 open_nocancel("/Users/krishna/.rstudio-desktop/history_database\0", 0x209, 0x1B6) = 20 0
423/0xcf5: 109517099 35 30 open_nocancel(".\0", 0x0, 0x1) = 20 0
423/0xcf5: 109576820 16 11 open_nocancel("/Users/krishna/.rstudio-desktop/sdb/s-3F25A09C/373AE888\0", 0x0, 0x1B6) = 21 0
423/0xcf5: 109673038 16 10 open_nocancel("/Users/krishna/.rstudio-desktop/sdb/s-3F25A09C/373AE888\0", 0x0, 0x1B6) = 21 0
The command sudo dtruss -a -t open_nocancel -p 423 will trace only the open_nocancel system call. Per the OS X man page for dtruss:
NAME
dtruss - process syscall details. Uses DTrace.
SYNOPSIS
dtruss [-acdeflhoLs] [-t syscall] { -p PID | -n name | command }
...
-t syscall
examine this syscall only
If you want to trace other system calls, you need to either change the -t ... argument, or remove it.

Unexpected behaviour in bash redirection

I found a strange and totally unexpected behaviour while working with redirection in bash and even If I manage to work around it, I'd like to know why it happens.
If I run this command:{ echo wtf > /dev/stdout ; } >> wtf.txt N times, I expect to see the filled with N "wtf" lines. What I found in the file is a single line.
I think that since the first command is opening /dev/stdout in truncate mode, then the mode is inherited by the second file descriptor (wtf.txt), which is then completely erased, but I'd like to know if some of you may explain it better and if this is the correct behaviour or a bug.
Just to be clear, the command I used was a different one, but with the echo example is simpler to understand it. The original command was a command who need an output file as argument and since I want the output on stdout I passed /dev/stdout as argument. The same behaviour may be verified with the command openssl rand -hex 4 -out /dev/stdout >> wtf.txt.
Finally, the solution I managed to fix the problem delegating the append operation to tee in the following way: { echo wtf > /dev/stdout } | tee -a wtf.txt > /dev/null
You can check what happens using strace:
strace -o wtf-trace.txt -ff bash -c '{ (echo wtf) > /dev/stdout; } >> wtf.txt'
This will generate two files like wtf-trace.txt.12889 and wtf-trace.txt.12890 in my case. What happens is, process 1 >> wtf.txt:
open("wtf.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3
dup2(3, 1) = 1
close(3) = 0
clone(child_stack=0, .................) = 12890
wait4(-1, [{WIFEXITED(s) .............) = 12890
exit_group(0) = ?
The first process opens or creates "wtf.txt" for appending and get FD 3. After that it duplicates FD 1 with FD 3 and closes FD 3. At this point it forks (clone), waits for it to exit and exits itself.
The second process { echo wtf > /dev/stdout } inherits the file by FD 1 (stdout) and it does:
open("/dev/stdout", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1) = 1
close(3) = 0
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(1, "wtf\n", 4) = 4
exit_group(0) = ?
As you can see it opens /dev/stdout (note O_TRUNC) and gets FD 3, dup2 to get FD 3 to FD 1, closes FD 3, checks FD 1 and gets a file with size of 0 st_size=0, writes to it and exits.
If you do | cat >> then the second process gets it FD 1 connected to a pipe, which is not seek-able or truncate-able...
NB: I show only the relevant lines of the files strace generated.

Mac OS X: GNU parallel can't find the number of cores on a remote server

I used homebrew to install GNU parallel on my mac so I can run some tests remotely on my University's servers. I was quickly running through the tutorials, but when I ran
parallel -S <username>#$SERVER1 echo running on ::: <username>#$SERVER1
I got the message
parallel: Warning: Could not figure out number of cpus on <username#server> (). Using 1.
Possibly related, I never added parallel to my path and got the warning that "parallel" wasn't a recognized command, but parallel ran anyways and still echo'd correctly. This particular server has 16 cores, how can I get parallel to recognize them?
GNU Parallel is less tested on OS X as I do not have access to an OS X installation, so you have likely found a bug.
GNU Parallel has since 20120322 used these to find the number of CPUs:
sysctl -n hw.physicalcpu
sysctl -a hw 2>/dev/null | grep [^a-z]physicalcpu[^a-z] | awk '{ print \$2 }'
And the number of cores:
sysctl -n hw.logicalcpu
sysctl -a hw 2>/dev/null | grep [^a-z]logicalcpu[^a-z] | awk '{ print \$2 }'
Can you test what output you get from those?
Which version of GNU Parallel are you using?
As a work around you can force GNU Parallel to detect 16 cores:
parallel -S 16/<username>#$SERVER1 echo running on ::: <username>#$SERVER1
Since version 20140422 you have been able to export your path to the remote server:
parallel --env PATH -S 16/<username>#$SERVER1 echo running on ::: <username>#$SERVER1
That way you just need to add the dir where parallel lives on the server to your path on local machine. E.g. parallel on the remote server is in /home/u/user/bin/parallel:
PATH=$PATH:/home/u/user/bin parallel --env PATH -S <username>#$SERVER1 echo running on ::: <username>#$SERVER1
Information for Ole
My iMac (OSX MAvericks on Intel core i7) gives the following, which all looks correct:
sysctl -n hw.physicalcpu
4
sysctl -a hw
hw.ncpu: 8
hw.byteorder: 1234
hw.memsize: 17179869184
hw.activecpu: 8
hw.physicalcpu: 4
hw.physicalcpu_max: 4
hw.logicalcpu: 8
hw.logicalcpu_max: 8
hw.cputype: 7
hw.cpusubtype: 4
hw.cpu64bit_capable: 1
hw.cpufamily: 1418770316
hw.cacheconfig: 8 2 2 8 0 0 0 0 0 0
hw.cachesize: 17179869184 32768 262144 8388608 0 0 0 0 0 0
hw.pagesize: 4096
hw.busfrequency: 100000000
hw.busfrequency_min: 100000000
hw.busfrequency_max: 100000000
hw.cpufrequency: 3400000000
hw.cpufrequency_min: 3400000000
hw.cpufrequency_max: 3400000000
hw.cachelinesize: 64
hw.l1icachesize: 32768
hw.l1dcachesize: 32768
hw.l2cachesize: 262144
hw.l3cachesize: 8388608
hw.tbfrequency: 1000000000
hw.packages: 1
hw.optional.floatingpoint: 1
hw.optional.mmx: 1
hw.optional.sse: 1
hw.optional.sse2: 1
hw.optional.sse3: 1
hw.optional.supplementalsse3: 1
hw.optional.sse4_1: 1
hw.optional.sse4_2: 1
hw.optional.x86_64: 1
hw.optional.aes: 1
hw.optional.avx1_0: 1
hw.optional.rdrand: 0
hw.optional.f16c: 0
hw.optional.enfstrg: 0
hw.optional.fma: 0
hw.optional.avx2_0: 0
hw.optional.bmi1: 0
hw.optional.bmi2: 0
hw.optional.rtm: 0
hw.optional.hle: 0
hw.cputhreadtype: 1
hw.machine = x86_64
hw.model = iMac12,2
hw.ncpu = 8
hw.byteorder = 1234
hw.physmem = 2147483648
hw.usermem = 521064448
hw.pagesize = 4096
hw.epoch = 0
hw.vectorunit = 1
hw.busfrequency = 100000000
hw.cpufrequency = 3400000000
hw.cachelinesize = 64
hw.l1icachesize = 32768
hw.l1dcachesize = 32768
hw.l2settings = 1
hw.l2cachesize = 262144
hw.l3settings = 1
hw.l3cachesize = 8388608
hw.tbfrequency = 1000000000
hw.memsize = 17179869184
hw.availcpu = 8
sysctl -n hw.logicalcpu
8

Why does there appear text on my command line even though I've redirected both STDOUT and STDERR to /dev/null?

I'm trying to unmount a encfs-filesystem from a script, but no matter how I try I seem unable to prevent the fuse error below to appear on screen/in crontab emails.
# exec 3>&1 1>/dev/null 4>&2 2>/dev/null; setsid fusermount -u /data/encfs; exec 1>&3 2>&4 3>&- 4>&-
# fuse failed. Common problems:
- fuse kernel module not installed (modprobe fuse)
- invalid options -- see usage message
The error itself I have to live with. The unmount is successfull and the error is false and due to a bug that is long gone in modern versions of fuse. I'm stuck with the older version since I'm on special hardware running a semi-ancient version of debian.
What annoys me is that I cannot tell the system to toss the nonsense message in /dev/null.
How does the message even appear on my screen after me using both setsid and redirects in my best efforts to prevent it?
EDIT:
# exec 3>&1 1>/dev/null 4>&2 2>/dev/null; setsid fusermount -u /data/encfs > /dev/null 2>&1; EXIT=$?; exec 1>&3 2>&4 3>&- 4>&-; echo $EXIT
0
# fuse failed. Common problems:
- fuse kernel module not installed (modprobe fuse)
- invalid options -- see usage message
I've even tried things like:
perl -e "`fusermount -u /data/encfs`"
But the error remain the same.
My /etc/syslog.conf:
auth,authpriv.* -/var/log/auth.log
*.*;auth,authpriv,cron.none -/var/log/syslog
cron.* -/var/log/cron.log
daemon.* -/var/log/daemon.log
kern.* -/var/log/kern.log
lpr.* -/var/log/lpr.log
mail.* -/var/log/mail.log
user.* -/var/log/user.log
*.=debug;\
auth,authpriv.none;\
mail.none -/var/log/debug
*.=info;*.=notice;*.=warn;\
auth,authpriv.none;\
cron,daemon.none;\
mail.none -/var/log/messages
EDIT2:
I don't think fusermount is the program actually generating the text. It pokes something else that does:
# strace -o ~/trash/strace.txt fusermount -u /data/encfs; EXIT=$?; echo $EXIT; grep write ~/trash/strace.txt
fuse failed. Common problems:
- fuse kernel module not installed (modprobe fuse)
- invalid options -- see usage message
0
write(5, "/dev/hdc1 / ext3 rw,noatime 0 0\n", 32) = 32
write(5, "proc /proc proc rw 0 0\n", 23) = 23
write(5, "devpts /dev/pts devpts rw 0 0\n", 30) = 30
write(5, "sysfs /sys sysfs rw 0 0\n", 24) = 24
write(5, "tmpfs /ramfs ramfs rw 0 0\n", 26) = 26
write(5, "tmpfs /USB tmpfs rw,size=16k 0 0"..., 33) = 33
write(5, "/dev/c/c /c ext3 rw,noatime,acl,"..., 65) = 65
write(5, "nfsd /proc/fs/nfsd nfsd rw 0 0\n", 31) = 31
write(5, "usbfs /proc/bus/usb usbfs rw 0 0"..., 33) = 33
write(5, "//localhost/smb /root/folder"..., 55) = 55
If I let strace log to stdout I get the error message in the middle of the umount system call:
# strace fusermount -u /data/encfs
execve("/usr/bin/fusermount", ["fusermount", "-u", "/data/encfs"], [/* 16 vars */]) = 0
[... abbreviating ...]
close(5) = 0
munmap(0x20020000, 16384) = 0
profil(0, 0, 0x2010c168, 0x4) = 0
umount("/data/encfs", 0fuse failed. Common problems:
- fuse kernel module not installed (modprobe fuse)
- invalid options -- see usage message
) = 0
profil(0, 0, 0x1177c, 0x20179f98) = 0
stat64("/etc/mtab", {st_mode=S_IFREG|0644, st_size=407, ...}) = 0
ftime(0x13840) = 0
Use strace on the command. It will show you details about what's going on, including the number of the descriptor to which the message is written
strace fsusermount -u /data/encfs
If the message comes from fsusermount you should see a line like
write(0, "- fuse kernel module not installed (modprobe fuse)\n")
somewhere in the output. The number (not necessarily 0) is the number of the file descriptor to which the message is written. Redirecting the descriptor with that number should get you rid of the message:
fsusermount -u /data/encfs 0>/dev/null
Figured it out.
The error message does not come from the fusermount, it comes from the mount command when fusermount runs.
Doing it like this fixes the problem:
# encfs --extpass=echo_key.sh /data/.encfs /data/encfs 2>/dev/null; sleep 3; fusermount -u /data/encfs
#
Feels so obvious now that I know...

Difference between "test -a file" and "test file -ef file"

QNX (Neutrino 6.5.0) uses an open source implementation of ksh as its shell. A lot of the provided scripts, including the system startup scripts, use constructs such as
if ! test /dev/slog -ef /dev/slog; then
# do something
fi
to check whether a resource manager exists or not in the filesystem. I've searched and could only find very dray explanations that -ef checks to see whether the two parameters are in fact the same file. Since the filename specified is the same it seems to just reduce to checking that the file exists.
I have checked the behaviour of test -a and test -e (both seem to check for file existance of any type of file according to the various docs I've read) and they seem to also work.
Is there any difference in the checks performed between -ef and -a/-e? Is using -ef some kind of attempt to protect against a race condition in the existence of the file?
Reviewing the strace on Ubuntu Linux's copy of ksh reveals no substantial differences. One call to stat vs two.
$ strace test /tmp/tmp.geLaoPkXXC -ef /tmp/tmp.geLaoPkXXC
showed this:
mmap(NULL, 7220736, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f11dc80b000
close(3) = 0
stat("/tmp/tmp.geLaoPkXXC", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
stat("/tmp/tmp.geLaoPkXXC", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
close(1) = 0
close(2) = 0
...whereas
$ strace test -a /tmp/tmp.geLaoPkXXC
showed this:
fstat(3, {st_mode=S_IFREG|0644, st_size=7220736, ...}) = 0
mmap(NULL, 7220736, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6b49e2b000
close(3) = 0
stat("/tmp/tmp.geLaoPkXXC", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
close(1) = 0
close(2) = 0
One stat vs two.
$ ksh --version
version sh (AT&T Research) 93u 2011-02-08
We don't know how the code use the stat exactly without code, we need to find the difference via the code.
/* code for -ef */
return (stat (argv[op - 1], &stat_buf) == 0
&& stat (argv[op + 1], &stat_spare) == 0
&& stat_buf.st_dev == stat_spare.st_dev
&& stat_buf.st_ino == stat_spare.st_ino);
/* code for -e/-a */
case 'a': /* file exists in the file system? */
case 'e':
return stat (argv[pos - 1], &stat_buf) == 0;
So, if the names are the same and two stat() with the same name will return the same value, then,
test -a/-e file is the same as test file -ef file. We know the first condition is true, and we know the second condition is also true from the comments from #tinman

Resources