609 lines
13 KiB
Ruby
Executable File
609 lines
13 KiB
Ruby
Executable File
#!/usr/bin/ruby
|
|
|
|
# A script to statically list syscalls used by a given binary.
|
|
#
|
|
# Syntax: list-syscalls.rb <binary> [--only-used-syscalls]
|
|
#
|
|
# NOTE: For accurate results, build the binary with musl and LTO enabled.
|
|
# Example: ./polkadot/scripts/list-syscalls/list-syscalls.rb target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls
|
|
#
|
|
# Author: @koute
|
|
# Source: https://gist.github.com/koute/166f82bfee5e27324077891008fca6eb
|
|
|
|
require 'shellwords'
|
|
require 'set'
|
|
|
|
SYNTAX_STRING = 'Syntax: list-syscalls.rb <binary> [--only-used-syscalls]'.freeze
|
|
|
|
# Generated from `libc` using the following regex:
|
|
# 'pub const SYS_([a-z0-9_]+): ::c_long = (\d+);'
|
|
# ' \2 => "\1",'
|
|
SYSCALLS = {
|
|
0 => 'read',
|
|
1 => 'write',
|
|
2 => 'open',
|
|
3 => 'close',
|
|
4 => 'stat',
|
|
5 => 'fstat',
|
|
6 => 'lstat',
|
|
7 => 'poll',
|
|
8 => 'lseek',
|
|
9 => 'mmap',
|
|
10 => 'mprotect',
|
|
11 => 'munmap',
|
|
12 => 'brk',
|
|
13 => 'rt_sigaction',
|
|
14 => 'rt_sigprocmask',
|
|
15 => 'rt_sigreturn',
|
|
16 => 'ioctl',
|
|
17 => 'pread64',
|
|
18 => 'pwrite64',
|
|
19 => 'readv',
|
|
20 => 'writev',
|
|
21 => 'access',
|
|
22 => 'pipe',
|
|
23 => 'select',
|
|
24 => 'sched_yield',
|
|
25 => 'mremap',
|
|
26 => 'msync',
|
|
27 => 'mincore',
|
|
28 => 'madvise',
|
|
29 => 'shmget',
|
|
30 => 'shmat',
|
|
31 => 'shmctl',
|
|
32 => 'dup',
|
|
33 => 'dup2',
|
|
34 => 'pause',
|
|
35 => 'nanosleep',
|
|
36 => 'getitimer',
|
|
37 => 'alarm',
|
|
38 => 'setitimer',
|
|
39 => 'getpid',
|
|
40 => 'sendfile',
|
|
41 => 'socket',
|
|
42 => 'connect',
|
|
43 => 'accept',
|
|
44 => 'sendto',
|
|
45 => 'recvfrom',
|
|
46 => 'sendmsg',
|
|
47 => 'recvmsg',
|
|
48 => 'shutdown',
|
|
49 => 'bind',
|
|
50 => 'listen',
|
|
51 => 'getsockname',
|
|
52 => 'getpeername',
|
|
53 => 'socketpair',
|
|
54 => 'setsockopt',
|
|
55 => 'getsockopt',
|
|
56 => 'clone',
|
|
57 => 'fork',
|
|
58 => 'vfork',
|
|
59 => 'execve',
|
|
60 => 'exit',
|
|
61 => 'wait4',
|
|
62 => 'kill',
|
|
63 => 'uname',
|
|
64 => 'semget',
|
|
65 => 'semop',
|
|
66 => 'semctl',
|
|
67 => 'shmdt',
|
|
68 => 'msgget',
|
|
69 => 'msgsnd',
|
|
70 => 'msgrcv',
|
|
71 => 'msgctl',
|
|
72 => 'fcntl',
|
|
73 => 'flock',
|
|
74 => 'fsync',
|
|
75 => 'fdatasync',
|
|
76 => 'truncate',
|
|
77 => 'ftruncate',
|
|
78 => 'getdents',
|
|
79 => 'getcwd',
|
|
80 => 'chdir',
|
|
81 => 'fchdir',
|
|
82 => 'rename',
|
|
83 => 'mkdir',
|
|
84 => 'rmdir',
|
|
85 => 'creat',
|
|
86 => 'link',
|
|
87 => 'unlink',
|
|
88 => 'symlink',
|
|
89 => 'readlink',
|
|
90 => 'chmod',
|
|
91 => 'fchmod',
|
|
92 => 'chown',
|
|
93 => 'fchown',
|
|
94 => 'lchown',
|
|
95 => 'umask',
|
|
96 => 'gettimeofday',
|
|
97 => 'getrlimit',
|
|
98 => 'getrusage',
|
|
99 => 'sysinfo',
|
|
100 => 'times',
|
|
101 => 'ptrace',
|
|
102 => 'getuid',
|
|
103 => 'syslog',
|
|
104 => 'getgid',
|
|
105 => 'setuid',
|
|
106 => 'setgid',
|
|
107 => 'geteuid',
|
|
108 => 'getegid',
|
|
109 => 'setpgid',
|
|
110 => 'getppid',
|
|
111 => 'getpgrp',
|
|
112 => 'setsid',
|
|
113 => 'setreuid',
|
|
114 => 'setregid',
|
|
115 => 'getgroups',
|
|
116 => 'setgroups',
|
|
117 => 'setresuid',
|
|
118 => 'getresuid',
|
|
119 => 'setresgid',
|
|
120 => 'getresgid',
|
|
121 => 'getpgid',
|
|
122 => 'setfsuid',
|
|
123 => 'setfsgid',
|
|
124 => 'getsid',
|
|
125 => 'capget',
|
|
126 => 'capset',
|
|
127 => 'rt_sigpending',
|
|
128 => 'rt_sigtimedwait',
|
|
129 => 'rt_sigqueueinfo',
|
|
130 => 'rt_sigsuspend',
|
|
131 => 'sigaltstack',
|
|
132 => 'utime',
|
|
133 => 'mknod',
|
|
134 => 'uselib',
|
|
135 => 'personality',
|
|
136 => 'ustat',
|
|
137 => 'statfs',
|
|
138 => 'fstatfs',
|
|
139 => 'sysfs',
|
|
140 => 'getpriority',
|
|
141 => 'setpriority',
|
|
142 => 'sched_setparam',
|
|
143 => 'sched_getparam',
|
|
144 => 'sched_setscheduler',
|
|
145 => 'sched_getscheduler',
|
|
146 => 'sched_get_priority_max',
|
|
147 => 'sched_get_priority_min',
|
|
148 => 'sched_rr_get_interval',
|
|
149 => 'mlock',
|
|
150 => 'munlock',
|
|
151 => 'mlockall',
|
|
152 => 'munlockall',
|
|
153 => 'vhangup',
|
|
154 => 'modify_ldt',
|
|
155 => 'pivot_root',
|
|
156 => '_sysctl',
|
|
157 => 'prctl',
|
|
158 => 'arch_prctl',
|
|
159 => 'adjtimex',
|
|
160 => 'setrlimit',
|
|
161 => 'chroot',
|
|
162 => 'sync',
|
|
163 => 'acct',
|
|
164 => 'settimeofday',
|
|
165 => 'mount',
|
|
166 => 'umount2',
|
|
167 => 'swapon',
|
|
168 => 'swapoff',
|
|
169 => 'reboot',
|
|
170 => 'sethostname',
|
|
171 => 'setdomainname',
|
|
172 => 'iopl',
|
|
173 => 'ioperm',
|
|
174 => 'create_module',
|
|
175 => 'init_module',
|
|
176 => 'delete_module',
|
|
177 => 'get_kernel_syms',
|
|
178 => 'query_module',
|
|
179 => 'quotactl',
|
|
180 => 'nfsservctl',
|
|
181 => 'getpmsg',
|
|
182 => 'putpmsg',
|
|
183 => 'afs_syscall',
|
|
184 => 'tuxcall',
|
|
185 => 'security',
|
|
186 => 'gettid',
|
|
187 => 'readahead',
|
|
188 => 'setxattr',
|
|
189 => 'lsetxattr',
|
|
190 => 'fsetxattr',
|
|
191 => 'getxattr',
|
|
192 => 'lgetxattr',
|
|
193 => 'fgetxattr',
|
|
194 => 'listxattr',
|
|
195 => 'llistxattr',
|
|
196 => 'flistxattr',
|
|
197 => 'removexattr',
|
|
198 => 'lremovexattr',
|
|
199 => 'fremovexattr',
|
|
200 => 'tkill',
|
|
201 => 'time',
|
|
202 => 'futex',
|
|
203 => 'sched_setaffinity',
|
|
204 => 'sched_getaffinity',
|
|
205 => 'set_thread_area',
|
|
206 => 'io_setup',
|
|
207 => 'io_destroy',
|
|
208 => 'io_getevents',
|
|
209 => 'io_submit',
|
|
210 => 'io_cancel',
|
|
211 => 'get_thread_area',
|
|
212 => 'lookup_dcookie',
|
|
213 => 'epoll_create',
|
|
214 => 'epoll_ctl_old',
|
|
215 => 'epoll_wait_old',
|
|
216 => 'remap_file_pages',
|
|
217 => 'getdents64',
|
|
218 => 'set_tid_address',
|
|
219 => 'restart_syscall',
|
|
220 => 'semtimedop',
|
|
221 => 'fadvise64',
|
|
222 => 'timer_create',
|
|
223 => 'timer_settime',
|
|
224 => 'timer_gettime',
|
|
225 => 'timer_getoverrun',
|
|
226 => 'timer_delete',
|
|
227 => 'clock_settime',
|
|
228 => 'clock_gettime',
|
|
229 => 'clock_getres',
|
|
230 => 'clock_nanosleep',
|
|
231 => 'exit_group',
|
|
232 => 'epoll_wait',
|
|
233 => 'epoll_ctl',
|
|
234 => 'tgkill',
|
|
235 => 'utimes',
|
|
236 => 'vserver',
|
|
237 => 'mbind',
|
|
238 => 'set_mempolicy',
|
|
239 => 'get_mempolicy',
|
|
240 => 'mq_open',
|
|
241 => 'mq_unlink',
|
|
242 => 'mq_timedsend',
|
|
243 => 'mq_timedreceive',
|
|
244 => 'mq_notify',
|
|
245 => 'mq_getsetattr',
|
|
246 => 'kexec_load',
|
|
247 => 'waitid',
|
|
248 => 'add_key',
|
|
249 => 'request_key',
|
|
250 => 'keyctl',
|
|
251 => 'ioprio_set',
|
|
252 => 'ioprio_get',
|
|
253 => 'inotify_init',
|
|
254 => 'inotify_add_watch',
|
|
255 => 'inotify_rm_watch',
|
|
256 => 'migrate_pages',
|
|
257 => 'openat',
|
|
258 => 'mkdirat',
|
|
259 => 'mknodat',
|
|
260 => 'fchownat',
|
|
261 => 'futimesat',
|
|
262 => 'newfstatat',
|
|
263 => 'unlinkat',
|
|
264 => 'renameat',
|
|
265 => 'linkat',
|
|
266 => 'symlinkat',
|
|
267 => 'readlinkat',
|
|
268 => 'fchmodat',
|
|
269 => 'faccessat',
|
|
270 => 'pselect6',
|
|
271 => 'ppoll',
|
|
272 => 'unshare',
|
|
273 => 'set_robust_list',
|
|
274 => 'get_robust_list',
|
|
275 => 'splice',
|
|
276 => 'tee',
|
|
277 => 'sync_file_range',
|
|
278 => 'vmsplice',
|
|
279 => 'move_pages',
|
|
280 => 'utimensat',
|
|
281 => 'epoll_pwait',
|
|
282 => 'signalfd',
|
|
283 => 'timerfd_create',
|
|
284 => 'eventfd',
|
|
285 => 'fallocate',
|
|
286 => 'timerfd_settime',
|
|
287 => 'timerfd_gettime',
|
|
288 => 'accept4',
|
|
289 => 'signalfd4',
|
|
290 => 'eventfd2',
|
|
291 => 'epoll_create1',
|
|
292 => 'dup3',
|
|
293 => 'pipe2',
|
|
294 => 'inotify_init1',
|
|
295 => 'preadv',
|
|
296 => 'pwritev',
|
|
297 => 'rt_tgsigqueueinfo',
|
|
298 => 'perf_event_open',
|
|
299 => 'recvmmsg',
|
|
300 => 'fanotify_init',
|
|
301 => 'fanotify_mark',
|
|
302 => 'prlimit64',
|
|
303 => 'name_to_handle_at',
|
|
304 => 'open_by_handle_at',
|
|
305 => 'clock_adjtime',
|
|
306 => 'syncfs',
|
|
307 => 'sendmmsg',
|
|
308 => 'setns',
|
|
309 => 'getcpu',
|
|
310 => 'process_vm_readv',
|
|
311 => 'process_vm_writev',
|
|
312 => 'kcmp',
|
|
313 => 'finit_module',
|
|
314 => 'sched_setattr',
|
|
315 => 'sched_getattr',
|
|
316 => 'renameat2',
|
|
317 => 'seccomp',
|
|
318 => 'getrandom',
|
|
319 => 'memfd_create',
|
|
320 => 'kexec_file_load',
|
|
321 => 'bpf',
|
|
322 => 'execveat',
|
|
323 => 'userfaultfd',
|
|
324 => 'membarrier',
|
|
325 => 'mlock2',
|
|
326 => 'copy_file_range',
|
|
327 => 'preadv2',
|
|
328 => 'pwritev2',
|
|
329 => 'pkey_mprotect',
|
|
330 => 'pkey_alloc',
|
|
331 => 'pkey_free',
|
|
332 => 'statx',
|
|
334 => 'rseq',
|
|
424 => 'pidfd_send_signal',
|
|
425 => 'io_uring_setup',
|
|
426 => 'io_uring_enter',
|
|
427 => 'io_uring_register',
|
|
428 => 'open_tree',
|
|
429 => 'move_mount',
|
|
430 => 'fsopen',
|
|
431 => 'fsconfig',
|
|
432 => 'fsmount',
|
|
433 => 'fspick',
|
|
434 => 'pidfd_open',
|
|
435 => 'clone3',
|
|
436 => 'close_range',
|
|
437 => 'openat2',
|
|
438 => 'pidfd_getfd',
|
|
439 => 'faccessat2',
|
|
440 => 'process_madvise',
|
|
441 => 'epoll_pwait2',
|
|
442 => 'mount_setattr',
|
|
443 => 'quotactl_fd',
|
|
444 => 'landlock_create_ruleset',
|
|
445 => 'landlock_add_rule',
|
|
446 => 'landlock_restrict_self',
|
|
447 => 'memfd_secret',
|
|
448 => 'process_mrelease',
|
|
449 => 'futex_waitv',
|
|
450 => 'set_mempolicy_home_node'
|
|
}.map { |num, name| [num, "#{num} (#{name})"] }.to_h
|
|
|
|
REGS_R64 = %w[
|
|
rax
|
|
rbx
|
|
rcx
|
|
rdx
|
|
rsi
|
|
rdi
|
|
rsp
|
|
rbp
|
|
r8
|
|
r9
|
|
r10
|
|
r11
|
|
r12
|
|
r13
|
|
r14
|
|
r15
|
|
]
|
|
|
|
REGS_R32 = %w[
|
|
eax
|
|
ebx
|
|
ecx
|
|
edx
|
|
esi
|
|
edi
|
|
esp
|
|
ebp
|
|
r8d
|
|
r9d
|
|
r10d
|
|
r11d
|
|
r12d
|
|
r13d
|
|
r14d
|
|
r15d
|
|
]
|
|
|
|
REGS_R16 = %w[
|
|
ax
|
|
bx
|
|
cx
|
|
dx
|
|
si
|
|
di
|
|
sp
|
|
bp
|
|
r8w
|
|
r9w
|
|
r10w
|
|
r11w
|
|
r12w
|
|
r13w
|
|
r14w
|
|
r15w
|
|
]
|
|
|
|
REGS_R8 = %w[
|
|
al
|
|
bl
|
|
cl
|
|
dl
|
|
sil
|
|
dil
|
|
spl
|
|
bpl
|
|
r8b
|
|
r9b
|
|
r10b
|
|
r11b
|
|
r12b
|
|
r13b
|
|
r14b
|
|
r15b
|
|
]
|
|
|
|
REG_MAP = (REGS_R64.map { |r| [r, r] } + REGS_R32.zip(REGS_R64) + REGS_R16.zip(REGS_R64) + REGS_R8.zip(REGS_R64)).to_h
|
|
REGS_R = (REGS_R64 + REGS_R32 + REGS_R16 + REGS_R8).join('|')
|
|
|
|
if ARGV.empty?
|
|
warn SYNTAX_STRING
|
|
exit 1
|
|
end
|
|
|
|
file_path = ARGV[0]
|
|
raise "no such file: #{file_path}" unless File.exist? file_path
|
|
|
|
only_used_syscalls = false
|
|
ARGV[1..].each do |arg|
|
|
if arg == '--only-used-syscalls'
|
|
only_used_syscalls = true
|
|
else
|
|
warn "invalid argument '#{arg}':\n#{SYNTAX_STRING}"
|
|
exit 1
|
|
end
|
|
end
|
|
|
|
puts 'Running objdump...' unless only_used_syscalls
|
|
dump = `objdump -wd -j .text -M intel #{file_path.shellescape}`
|
|
raise 'objdump failed' unless $?.exitstatus == 0
|
|
|
|
puts 'Parsing objdump output...' unless only_used_syscalls
|
|
current_fn = nil
|
|
code_for_fn = {}
|
|
fns_with_syscall = Set.new
|
|
fns_with_indirect_syscall = Set.new
|
|
dump.split("\n").each do |line|
|
|
if line =~ /\A[0-9a-f]+ <(.+?)>:/
|
|
current_fn = Regexp.last_match(1)
|
|
next
|
|
end
|
|
|
|
next unless current_fn
|
|
next if %w[syscall __syscall_cp_c].include?(current_fn) # These are for indirect syscalls.
|
|
|
|
code = line.strip.split("\t")[2]
|
|
next if [nil, ''].include?(code)
|
|
|
|
code_for_fn[current_fn] ||= []
|
|
code_for_fn[current_fn] << code.gsub(/[\t ]+/, ' ')
|
|
|
|
fns_with_syscall.add(current_fn) if code == 'syscall'
|
|
fns_with_indirect_syscall.add(current_fn) if code =~ /<(syscall|__syscall_cp)>/
|
|
end
|
|
|
|
unless only_used_syscalls
|
|
puts "Found #{fns_with_syscall.length} functions doing direct syscalls"
|
|
puts "Found #{fns_with_indirect_syscall.length} functions doing indirect syscalls"
|
|
end
|
|
|
|
syscalls_for_fn = {}
|
|
not_found_count = 0
|
|
(fns_with_syscall + fns_with_indirect_syscall).each do |fn_name|
|
|
syscalls_for_fn[fn_name] ||= []
|
|
|
|
if fn_name =~ /_ZN11parking_lot9raw_mutex8RawMutex9lock_slow.+/
|
|
# Hardcode 'SYS_futex' as this function produces a really messy assembly.
|
|
syscalls_for_fn[fn_name] << 202
|
|
next
|
|
end
|
|
|
|
code = code_for_fn[fn_name]
|
|
|
|
found = false
|
|
regs = {}
|
|
code.each do |inst|
|
|
if inst =~ /mov (#{REGS_R}),(.+)/
|
|
reg = Regexp.last_match(1)
|
|
value = Regexp.last_match(2)
|
|
regs[REG_MAP[reg]] = if value =~ /#{REGS_R}/
|
|
regs[REG_MAP[value]]
|
|
elsif value =~ /0x([0-9a-f]+)/
|
|
Regexp.last_match(1).to_i(16)
|
|
end
|
|
elsif inst =~ /xor (#{REGS_R}),(#{REGS_R})/
|
|
reg_1 = Regexp.last_match(1)
|
|
reg_2 = Regexp.last_match(1)
|
|
regs[REG_MAP[reg_1]] = 0 if reg_1 == reg_2
|
|
elsif inst =~ /lea (#{REGS_R}),(.+)/
|
|
reg = Regexp.last_match(1)
|
|
value = Regexp.last_match(2)
|
|
regs[REG_MAP[reg]] = ('syscall' if value.strip =~ /\[rip\+0x[a-z0-9]+\]\s*#\s*[0-9a-f]+\s*<syscall>/)
|
|
elsif inst =~ /(call|jmp) (#{REGS_R})/
|
|
reg = Regexp.last_match(2)
|
|
if regs[REG_MAP[reg]] == 'syscall'
|
|
if !regs['rdi'].nil?
|
|
syscalls_for_fn[fn_name] << regs['rdi']
|
|
found = true
|
|
else
|
|
found = false
|
|
end
|
|
end
|
|
elsif inst =~ /(call|jmp) [0-9a-f]+ <(syscall|__syscall_cp)>/
|
|
if !regs['rdi'].nil?
|
|
syscalls_for_fn[fn_name] << regs['rdi']
|
|
found = true
|
|
else
|
|
found = false
|
|
end
|
|
elsif inst == 'syscall'
|
|
if !regs['rax'].nil?
|
|
syscalls_for_fn[fn_name] << regs['rax']
|
|
found = true
|
|
else
|
|
found = false
|
|
end
|
|
end
|
|
end
|
|
|
|
next if found
|
|
|
|
puts "WARN: Function triggers a syscall but couldn't figure out which one: #{fn_name}"
|
|
puts ' ' + code.join("\n ")
|
|
puts
|
|
not_found_count += 1
|
|
end
|
|
|
|
puts "WARN: Failed to figure out syscall for #{not_found_count} function(s)" if not_found_count > 0
|
|
|
|
fns_for_syscall = {}
|
|
syscalls_for_fn.each do |fn_name, syscalls|
|
|
syscalls.each do |syscall|
|
|
fns_for_syscall[syscall] ||= []
|
|
fns_for_syscall[syscall] << fn_name
|
|
end
|
|
end
|
|
|
|
if only_used_syscalls
|
|
puts syscalls_for_fn.values.flatten.sort.uniq.map { |sc| SYSCALLS[sc] || sc }.join("\n")
|
|
else
|
|
puts 'Functions per syscall:'
|
|
fns_for_syscall.sort_by { |sc, _| sc }.each do |syscall, fn_names|
|
|
fn_names = fn_names.sort.uniq
|
|
|
|
puts " #{SYSCALLS[syscall] || syscall} [#{fn_names.length} functions]"
|
|
fn_names.each do |fn_name|
|
|
puts " #{fn_name}"
|
|
end
|
|
end
|
|
|
|
puts
|
|
puts 'Used syscalls:'
|
|
puts ' ' + syscalls_for_fn.values.flatten.sort.uniq.map { |sc| SYSCALLS[sc] || sc }.join("\n ")
|
|
end
|