rptrace 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,355 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fcntl"
4
+
5
+ module Rptrace
6
+ # Renderable syscall event (enter/exit).
7
+ class SyscallEvent
8
+ # Map of errno number to symbolic name (e.g., 2 => "ENOENT").
9
+ ERRNO_NAME_BY_NUMBER = Errno.constants.each_with_object({}) do |const_name, map|
10
+ errno_class = Errno.const_get(const_name)
11
+ next unless errno_class.is_a?(Class) && errno_class < SystemCallError
12
+ next unless errno_class.const_defined?(:Errno)
13
+
14
+ map[errno_class::Errno] ||= const_name.to_s
15
+ rescue NameError
16
+ next
17
+ end.freeze
18
+ # Linux kernel upper bound for -errno syscall return convention.
19
+ LINUX_MAX_ERRNO = 4095
20
+ # Access mode mask for open/openat flags.
21
+ OPEN_ACCESS_MASK = Fcntl.const_defined?(:O_ACCMODE) ? Fcntl::O_ACCMODE : 0x3
22
+ # Mapping of open access mode value to symbolic name.
23
+ OPEN_ACCESS_NAMES = {
24
+ (Fcntl.const_defined?(:O_RDONLY) ? Fcntl::O_RDONLY : 0) => "O_RDONLY",
25
+ (Fcntl.const_defined?(:O_WRONLY) ? Fcntl::O_WRONLY : 1) => "O_WRONLY",
26
+ (Fcntl.const_defined?(:O_RDWR) ? Fcntl::O_RDWR : 2) => "O_RDWR"
27
+ }.freeze
28
+ # Mapping of open/openat modifier bits to symbolic names.
29
+ OPEN_FLAG_NAMES = %i[
30
+ O_APPEND O_ASYNC O_CLOEXEC O_CREAT O_DIRECT O_DIRECTORY O_DSYNC O_EXCL O_LARGEFILE
31
+ O_NOATIME O_NOCTTY O_NOFOLLOW O_NONBLOCK O_PATH O_SYNC O_TMPFILE O_TRUNC
32
+ ].each_with_object({}) do |const_name, map|
33
+ next unless Fcntl.const_defined?(const_name)
34
+
35
+ value = Fcntl.const_get(const_name)
36
+ next unless value.is_a?(Integer) && value.positive?
37
+
38
+ map[value] = const_name.to_s
39
+ end.freeze
40
+ # Mapping of PROT_* values used by mmap/mprotect.
41
+ PROT_NAMES = %i[
42
+ PROT_EXEC PROT_GROWSDOWN PROT_GROWSUP PROT_NONE PROT_READ PROT_SEM PROT_WRITE
43
+ ].each_with_object({}) do |const_name, map|
44
+ next unless Fcntl.const_defined?(const_name)
45
+
46
+ value = Fcntl.const_get(const_name)
47
+ next unless value.is_a?(Integer)
48
+
49
+ map[value] = const_name.to_s
50
+ end.freeze
51
+ # Bit mask for MAP_* type field.
52
+ MAP_TYPE_MASK = Fcntl.const_defined?(:MAP_TYPE) ? Fcntl::MAP_TYPE : nil
53
+ # Mapping of mmap type flags.
54
+ MAP_TYPE_NAMES = %i[
55
+ MAP_PRIVATE MAP_SHARED MAP_SHARED_VALIDATE
56
+ ].each_with_object({}) do |const_name, map|
57
+ next unless Fcntl.const_defined?(const_name)
58
+
59
+ value = Fcntl.const_get(const_name)
60
+ next unless value.is_a?(Integer)
61
+
62
+ map[value] = const_name.to_s
63
+ end.freeze
64
+ # Mapping of additional mmap modifier bits.
65
+ MAP_FLAG_NAMES = %i[
66
+ MAP_32BIT MAP_ANONYMOUS MAP_ANON MAP_DENYWRITE MAP_EXECUTABLE MAP_FILE
67
+ MAP_FIXED MAP_FIXED_NOREPLACE MAP_GROWSDOWN MAP_HUGETLB MAP_LOCKED MAP_NONBLOCK
68
+ MAP_NORESERVE MAP_POPULATE MAP_STACK MAP_SYNC MAP_UNINITIALIZED
69
+ ].each_with_object({}) do |const_name, map|
70
+ next unless Fcntl.const_defined?(const_name)
71
+
72
+ value = Fcntl.const_get(const_name)
73
+ next unless value.is_a?(Integer) && value.positive?
74
+
75
+ map[value] ||= const_name.to_s
76
+ end.freeze
77
+ # Mapping of clone(2) flag bits in the upper bits of clone flags argument.
78
+ CLONE_FLAG_NAMES = {
79
+ 0x0000_0100 => "CLONE_VM",
80
+ 0x0000_0200 => "CLONE_FS",
81
+ 0x0000_0400 => "CLONE_FILES",
82
+ 0x0000_0800 => "CLONE_SIGHAND",
83
+ 0x0000_1000 => "CLONE_PIDFD",
84
+ 0x0000_2000 => "CLONE_PTRACE",
85
+ 0x0000_4000 => "CLONE_VFORK",
86
+ 0x0000_8000 => "CLONE_PARENT",
87
+ 0x0001_0000 => "CLONE_THREAD",
88
+ 0x0002_0000 => "CLONE_NEWNS",
89
+ 0x0004_0000 => "CLONE_SYSVSEM",
90
+ 0x0008_0000 => "CLONE_SETTLS",
91
+ 0x0010_0000 => "CLONE_PARENT_SETTID",
92
+ 0x0020_0000 => "CLONE_CHILD_CLEARTID",
93
+ 0x0040_0000 => "CLONE_DETACHED",
94
+ 0x0080_0000 => "CLONE_UNTRACED",
95
+ 0x0100_0000 => "CLONE_CHILD_SETTID",
96
+ 0x0200_0000 => "CLONE_NEWCGROUP",
97
+ 0x0400_0000 => "CLONE_NEWUTS",
98
+ 0x0800_0000 => "CLONE_NEWIPC",
99
+ 0x1000_0000 => "CLONE_NEWUSER",
100
+ 0x2000_0000 => "CLONE_NEWPID",
101
+ 0x4000_0000 => "CLONE_NEWNET",
102
+ 0x8000_0000 => "CLONE_IO"
103
+ }.freeze
104
+ # Mapping of wait4(2) option bits.
105
+ WAIT_OPTION_NAMES = {
106
+ Constants::WNOHANG => "WNOHANG",
107
+ Constants::WUNTRACED => "WUNTRACED",
108
+ Constants::WCONTINUED => "WCONTINUED",
109
+ Constants::WALL => "__WALL"
110
+ }.freeze
111
+
112
+ attr_reader :tracee, :syscall, :args, :return_value, :phase
113
+
114
+ def initialize(tracee:, syscall:, args:, phase:, return_value: nil)
115
+ @tracee = tracee
116
+ @syscall = syscall
117
+ @args = args
118
+ @phase = phase
119
+ @return_value = return_value
120
+ end
121
+
122
+ # @return [Boolean]
123
+ def enter?
124
+ phase == :enter
125
+ end
126
+
127
+ # @return [Boolean]
128
+ def exit?
129
+ phase == :exit
130
+ end
131
+
132
+ # @return [String]
133
+ def to_s
134
+ call = "#{syscall.name}(#{formatted_args})"
135
+ return "#{call} ..." if enter?
136
+
137
+ "#{call} = #{formatted_return_value}"
138
+ end
139
+
140
+ # @return [String]
141
+ def formatted_args
142
+ args.each_with_index.map do |value, index|
143
+ type = syscall.arg_types.fetch(index, nil)
144
+ format_argument(type, value, index)
145
+ end.join(", ")
146
+ end
147
+
148
+ # @return [String]
149
+ def formatted_return_value
150
+ return "?" if return_value.nil?
151
+ return return_value.to_s unless syscall_error_code?(return_value)
152
+
153
+ errno = -return_value
154
+ name = ERRNO_NAME_BY_NUMBER.fetch(errno, "ERRNO_#{errno}")
155
+ message = SystemCallError.new(errno).message
156
+ "-1 #{name} (#{message})"
157
+ end
158
+
159
+ private
160
+
161
+ def format_argument(type, value, index)
162
+ case type
163
+ when :str
164
+ format_string_pointer(value)
165
+ when :ptr, :buf
166
+ format_pointer(value)
167
+ when :flags
168
+ format_flags(value, index: index)
169
+ when :mode
170
+ format_mode(value)
171
+ when :fd, :int, :uint, :size, :pid
172
+ Integer(value).to_s
173
+ else
174
+ value.inspect
175
+ end
176
+ end
177
+
178
+ def format_string_pointer(value)
179
+ return "NULL" if pointer_null?(value)
180
+
181
+ tracee.memory.read_string(Integer(value)).inspect
182
+ rescue Rptrace::Error, StandardError
183
+ format_pointer(value)
184
+ end
185
+
186
+ def format_pointer(value)
187
+ return "NULL" if pointer_null?(value)
188
+
189
+ format("0x%x", Integer(value))
190
+ end
191
+
192
+ def format_flags(value, index:)
193
+ number = Integer(value)
194
+
195
+ if open_flags_argument?(index)
196
+ decode_open_flags(number)
197
+ elsif mmap_prot_argument?(index)
198
+ decode_mmap_prot(number)
199
+ elsif mmap_flags_argument?(index)
200
+ decode_mmap_flags(number)
201
+ elsif clone_flags_argument?(index)
202
+ decode_clone_flags(number)
203
+ elsif wait_options_argument?(index)
204
+ decode_wait_options(number)
205
+ else
206
+ format("0x%x", number)
207
+ end
208
+ end
209
+
210
+ def format_mode(value)
211
+ number = Integer(value)
212
+ return "0" if number.zero?
213
+
214
+ format("0%o", number)
215
+ end
216
+
217
+ def open_flags_argument?(index)
218
+ return false unless %i[open openat].include?(syscall.name)
219
+
220
+ flag_arg_name = syscall.arg_names.fetch(index, nil)
221
+ flag_arg_name == :flags
222
+ end
223
+
224
+ def mmap_prot_argument?(index)
225
+ return false unless syscall.name == :mmap
226
+
227
+ syscall.arg_names.fetch(index, nil) == :prot
228
+ end
229
+
230
+ def mmap_flags_argument?(index)
231
+ return false unless syscall.name == :mmap
232
+
233
+ syscall.arg_names.fetch(index, nil) == :flags
234
+ end
235
+
236
+ def clone_flags_argument?(index)
237
+ return false unless syscall.name == :clone
238
+
239
+ syscall.arg_names.fetch(index, nil) == :flags
240
+ end
241
+
242
+ def wait_options_argument?(index)
243
+ return false unless syscall.name == :wait4
244
+
245
+ syscall.arg_names.fetch(index, nil) == :options
246
+ end
247
+
248
+ def decode_open_flags(value)
249
+ names = []
250
+
251
+ access = value & OPEN_ACCESS_MASK
252
+ names << OPEN_ACCESS_NAMES.fetch(access, format("0x%x", access))
253
+
254
+ remaining = value & ~OPEN_ACCESS_MASK
255
+ OPEN_FLAG_NAMES.keys.sort.reverse_each do |bit|
256
+ next if bit.zero?
257
+ next unless (remaining & bit) == bit
258
+
259
+ names << OPEN_FLAG_NAMES.fetch(bit)
260
+ remaining &= ~bit
261
+ end
262
+
263
+ names << format("0x%x", remaining) unless remaining.zero?
264
+ names.join("|")
265
+ end
266
+
267
+ def decode_mmap_prot(value)
268
+ return PROT_NAMES.fetch(0, "0") if value.zero?
269
+
270
+ names = []
271
+ remaining = value
272
+ PROT_NAMES.keys.sort.each do |bit|
273
+ next if bit.zero?
274
+ next unless (remaining & bit) == bit
275
+
276
+ names << PROT_NAMES.fetch(bit)
277
+ remaining &= ~bit
278
+ end
279
+
280
+ names << format("0x%x", remaining) unless remaining.zero?
281
+ names.empty? ? format("0x%x", value) : names.join("|")
282
+ end
283
+
284
+ def decode_mmap_flags(value)
285
+ names = []
286
+ remaining = value
287
+
288
+ if MAP_TYPE_MASK
289
+ map_type = value & MAP_TYPE_MASK
290
+ names << MAP_TYPE_NAMES.fetch(map_type, format("0x%x", map_type)) unless map_type.zero?
291
+ remaining &= ~MAP_TYPE_MASK
292
+ end
293
+
294
+ MAP_FLAG_NAMES.keys.sort.each do |bit|
295
+ next unless (remaining & bit) == bit
296
+
297
+ names << MAP_FLAG_NAMES.fetch(bit)
298
+ remaining &= ~bit
299
+ end
300
+
301
+ names << format("0x%x", remaining) unless remaining.zero?
302
+ names.empty? ? format("0x%x", value) : names.join("|")
303
+ end
304
+
305
+ def decode_clone_flags(value)
306
+ names = []
307
+ exit_signal = value & 0xFF
308
+ remaining = value & ~0xFF
309
+
310
+ CLONE_FLAG_NAMES.keys.sort.each do |bit|
311
+ next unless (remaining & bit) == bit
312
+
313
+ names << CLONE_FLAG_NAMES.fetch(bit)
314
+ remaining &= ~bit
315
+ end
316
+
317
+ names << format_clone_exit_signal(exit_signal) unless exit_signal.zero?
318
+ names << format("0x%x", remaining) unless remaining.zero?
319
+ names.empty? ? "0" : names.join("|")
320
+ end
321
+
322
+ def format_clone_exit_signal(signal)
323
+ signal_name = Signal.signame(signal)
324
+ signal_name.start_with?("SIG") ? signal_name : "SIG#{signal_name}"
325
+ rescue ArgumentError
326
+ signal.to_s
327
+ end
328
+
329
+ def decode_wait_options(value)
330
+ return "0" if value.zero?
331
+
332
+ names = []
333
+ remaining = value
334
+ WAIT_OPTION_NAMES.keys.sort.each do |bit|
335
+ next unless (remaining & bit) == bit
336
+
337
+ names << WAIT_OPTION_NAMES.fetch(bit)
338
+ remaining &= ~bit
339
+ end
340
+
341
+ names << format("0x%x", remaining) unless remaining.zero?
342
+ names.empty? ? format("0x%x", value) : names.join("|")
343
+ end
344
+
345
+ def pointer_null?(value)
346
+ Integer(value).zero?
347
+ rescue StandardError
348
+ false
349
+ end
350
+
351
+ def syscall_error_code?(value)
352
+ value.is_a?(Integer) && value.negative? && (-value) <= LINUX_MAX_ERRNO
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rptrace
4
+ module SyscallTable
5
+ # aarch64 syscall table.
6
+ module AARCH64
7
+ # @return [Hash{Integer => Rptrace::Syscall::SyscallInfo}]
8
+ TABLE = {
9
+ 56 => Syscall::SyscallInfo.new(number: 56, name: :openat, arg_names: %i[dirfd pathname flags mode], arg_types: %i[fd str flags mode]),
10
+ 57 => Syscall::SyscallInfo.new(number: 57, name: :close, arg_names: %i[fd], arg_types: %i[fd]),
11
+ 63 => Syscall::SyscallInfo.new(number: 63, name: :read, arg_names: %i[fd buf count], arg_types: %i[fd buf size]),
12
+ 64 => Syscall::SyscallInfo.new(number: 64, name: :write, arg_names: %i[fd buf count], arg_types: %i[fd buf size]),
13
+ 93 => Syscall::SyscallInfo.new(number: 93, name: :exit, arg_names: %i[status], arg_types: %i[int]),
14
+ 94 => Syscall::SyscallInfo.new(number: 94, name: :exit_group, arg_names: %i[status], arg_types: %i[int]),
15
+ 221 => Syscall::SyscallInfo.new(number: 221, name: :execve, arg_names: %i[filename argv envp], arg_types: %i[str ptr ptr])
16
+ }.freeze
17
+
18
+ # @return [Hash{Symbol => Rptrace::Syscall::SyscallInfo}]
19
+ BY_NAME = TABLE.each_with_object({}) do |(_number, info), map|
20
+ map[info.name] = info
21
+ end.freeze
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rptrace
4
+ module SyscallTable
5
+ # Generates syscall table Ruby files from Linux unistd headers.
6
+ module Generator
7
+ # Raised when syscall header cannot be resolved for an architecture.
8
+ class HeaderNotFoundError < ArgumentError; end
9
+
10
+ module_function
11
+
12
+ # Header lookup and output mapping by architecture.
13
+ ARCH_CONFIG = {
14
+ x86_64: {
15
+ module_name: "X86_64",
16
+ output_path: "lib/rptrace/syscall_table/x86_64.rb",
17
+ env_key: "PTRACE_SYSCALL_HEADER_X86_64",
18
+ header_candidates: [
19
+ "/usr/include/x86_64-linux-gnu/asm/unistd_64.h",
20
+ "/usr/include/asm/unistd_64.h",
21
+ "/usr/include/x86_64-linux-gnu/asm/unistd.h"
22
+ ].freeze
23
+ }.freeze,
24
+ aarch64: {
25
+ module_name: "AARCH64",
26
+ output_path: "lib/rptrace/syscall_table/aarch64.rb",
27
+ env_key: "PTRACE_SYSCALL_HEADER_AARCH64",
28
+ header_candidates: [
29
+ "/usr/include/aarch64-linux-gnu/asm/unistd.h",
30
+ "/usr/aarch64-linux-gnu/include/asm/unistd.h",
31
+ "/usr/include/asm-generic/unistd.h"
32
+ ].freeze
33
+ }.freeze
34
+ }.freeze
35
+
36
+ # Matches numeric __NR_* macro definitions.
37
+ DEFINE_REGEX = /^\s*#\s*define\s+__NR(?:3264)?_([a-zA-Z0-9_]+)\s+([0-9]+)\b/.freeze
38
+
39
+ # Generates tables for all configured architectures.
40
+ #
41
+ # @param root_dir [String]
42
+ # @param arches [Array<Symbol>]
43
+ # @param skip_missing [Boolean] skip architectures without headers
44
+ # @return [Array<Hash>]
45
+ def generate_all(root_dir: Dir.pwd, arches: ARCH_CONFIG.keys, skip_missing: false)
46
+ generated = []
47
+
48
+ arches.each do |arch|
49
+ begin
50
+ generated << generate_for(arch, root_dir: root_dir)
51
+ rescue HeaderNotFoundError
52
+ raise unless skip_missing
53
+ end
54
+ end
55
+
56
+ generated
57
+ end
58
+
59
+ # Generates tables and reports skipped architectures.
60
+ #
61
+ # @param root_dir [String]
62
+ # @param arches [Array<Symbol>]
63
+ # @return [Hash]
64
+ def generate_available(root_dir: Dir.pwd, arches: ARCH_CONFIG.keys)
65
+ generated = []
66
+ skipped = []
67
+
68
+ arches.each do |arch|
69
+ begin
70
+ generated << generate_for(arch, root_dir: root_dir)
71
+ rescue HeaderNotFoundError => e
72
+ skipped << { arch: arch.to_sym, reason: e.message }
73
+ end
74
+ end
75
+
76
+ { generated: generated, skipped: skipped }
77
+ end
78
+
79
+ # Generates one syscall table file.
80
+ #
81
+ # @param arch [Symbol]
82
+ # @param root_dir [String]
83
+ # @return [Hash]
84
+ def generate_for(arch, root_dir: Dir.pwd)
85
+ config = ARCH_CONFIG.fetch(arch.to_sym) do
86
+ raise ArgumentError, "unsupported arch: #{arch}"
87
+ end
88
+ header_path = resolve_header_path(config)
89
+ entries = parse_header(File.read(header_path))
90
+ raise ArgumentError, "no syscall entries found in #{header_path}" if entries.empty?
91
+
92
+ output_path = File.expand_path(config.fetch(:output_path), root_dir)
93
+ File.write(output_path, render_table(module_name: config.fetch(:module_name), entries: entries))
94
+
95
+ {
96
+ arch: arch.to_sym,
97
+ header_path: header_path,
98
+ output_path: output_path,
99
+ entries_count: entries.size
100
+ }
101
+ end
102
+
103
+ # Parses #define __NR_* entries from header content.
104
+ #
105
+ # @param content [String]
106
+ # @return [Array<(Integer, Symbol)>]
107
+ def parse_header(content)
108
+ by_number = {}
109
+
110
+ content.each_line do |line|
111
+ match = line.match(DEFINE_REGEX)
112
+ next unless match
113
+
114
+ name = match[1].to_sym
115
+ number = Integer(match[2], 10)
116
+ by_number[number] ||= name
117
+ end
118
+
119
+ by_number.sort_by { |number, _name| number }
120
+ end
121
+
122
+ # Renders a syscall table Ruby source.
123
+ #
124
+ # @param module_name [String]
125
+ # @param entries [Array<(Integer, Symbol)>]
126
+ # @return [String]
127
+ def render_table(module_name:, entries:)
128
+ table_lines = entries.map do |number, name|
129
+ " #{number} => Syscall::SyscallInfo.new(number: #{number}, name: :#{name}, arg_names: [], arg_types: [])"
130
+ end
131
+
132
+ <<~RUBY
133
+ # frozen_string_literal: true
134
+
135
+ module Rptrace
136
+ module SyscallTable
137
+ module #{module_name}
138
+ TABLE = {
139
+ #{table_lines.join(",\n")}
140
+ }.freeze
141
+
142
+ BY_NAME = TABLE.each_with_object({}) do |(_number, info), map|
143
+ map[info.name] = info
144
+ end.freeze
145
+ end
146
+ end
147
+ end
148
+ RUBY
149
+ end
150
+
151
+ def resolve_header_path(config)
152
+ env_key = config.fetch(:env_key)
153
+ env_value = ENV[env_key]
154
+
155
+ if env_value && !env_value.empty?
156
+ expanded = File.expand_path(env_value)
157
+ return expanded if File.file?(expanded)
158
+
159
+ raise HeaderNotFoundError, "header path from #{env_key} does not exist: #{expanded}"
160
+ end
161
+
162
+ config.fetch(:header_candidates).each do |candidate|
163
+ expanded = File.expand_path(candidate)
164
+ return expanded if File.file?(expanded)
165
+ end
166
+
167
+ raise HeaderNotFoundError, "no syscall header found. set #{env_key}=<path-to-header>"
168
+ end
169
+ private_class_method :resolve_header_path
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rptrace
4
+ # Built-in syscall number table modules.
5
+ module SyscallTable
6
+ # x86_64 syscall table.
7
+ module X86_64
8
+ # @return [Hash{Integer => Rptrace::Syscall::SyscallInfo}]
9
+ TABLE = {
10
+ 0 => Syscall::SyscallInfo.new(number: 0, name: :read, arg_names: [], arg_types: []),
11
+ 1 => Syscall::SyscallInfo.new(number: 1, name: :write, arg_names: [], arg_types: []),
12
+ 2 => Syscall::SyscallInfo.new(number: 2, name: :open, arg_names: [], arg_types: []),
13
+ 3 => Syscall::SyscallInfo.new(number: 3, name: :close, arg_names: [], arg_types: []),
14
+ 4 => Syscall::SyscallInfo.new(number: 4, name: :stat, arg_names: [], arg_types: []),
15
+ 5 => Syscall::SyscallInfo.new(number: 5, name: :fstat, arg_names: [], arg_types: []),
16
+ 6 => Syscall::SyscallInfo.new(number: 6, name: :lstat, arg_names: [], arg_types: []),
17
+ 7 => Syscall::SyscallInfo.new(number: 7, name: :poll, arg_names: [], arg_types: []),
18
+ 8 => Syscall::SyscallInfo.new(number: 8, name: :lseek, arg_names: [], arg_types: []),
19
+ 9 => Syscall::SyscallInfo.new(number: 9, name: :mmap, arg_names: [], arg_types: []),
20
+ 10 => Syscall::SyscallInfo.new(number: 10, name: :mprotect, arg_names: [], arg_types: []),
21
+ 11 => Syscall::SyscallInfo.new(number: 11, name: :munmap, arg_names: [], arg_types: []),
22
+ 12 => Syscall::SyscallInfo.new(number: 12, name: :brk, arg_names: [], arg_types: []),
23
+ 16 => Syscall::SyscallInfo.new(number: 16, name: :ioctl, arg_names: [], arg_types: []),
24
+ 21 => Syscall::SyscallInfo.new(number: 21, name: :access, arg_names: [], arg_types: []),
25
+ 22 => Syscall::SyscallInfo.new(number: 22, name: :pipe, arg_names: [], arg_types: []),
26
+ 23 => Syscall::SyscallInfo.new(number: 23, name: :select, arg_names: [], arg_types: []),
27
+ 32 => Syscall::SyscallInfo.new(number: 32, name: :dup, arg_names: [], arg_types: []),
28
+ 33 => Syscall::SyscallInfo.new(number: 33, name: :dup2, arg_names: [], arg_types: []),
29
+ 41 => Syscall::SyscallInfo.new(number: 41, name: :socket, arg_names: [], arg_types: []),
30
+ 42 => Syscall::SyscallInfo.new(number: 42, name: :connect, arg_names: [], arg_types: []),
31
+ 43 => Syscall::SyscallInfo.new(number: 43, name: :accept, arg_names: [], arg_types: []),
32
+ 44 => Syscall::SyscallInfo.new(number: 44, name: :sendto, arg_names: [], arg_types: []),
33
+ 45 => Syscall::SyscallInfo.new(number: 45, name: :recvfrom, arg_names: [], arg_types: []),
34
+ 48 => Syscall::SyscallInfo.new(number: 48, name: :shutdown, arg_names: [], arg_types: []),
35
+ 49 => Syscall::SyscallInfo.new(number: 49, name: :bind, arg_names: [], arg_types: []),
36
+ 50 => Syscall::SyscallInfo.new(number: 50, name: :listen, arg_names: [], arg_types: []),
37
+ 56 => Syscall::SyscallInfo.new(number: 56, name: :clone, arg_names: [], arg_types: []),
38
+ 57 => Syscall::SyscallInfo.new(number: 57, name: :fork, arg_names: [], arg_types: []),
39
+ 58 => Syscall::SyscallInfo.new(number: 58, name: :vfork, arg_names: [], arg_types: []),
40
+ 59 => Syscall::SyscallInfo.new(number: 59, name: :execve, arg_names: [], arg_types: []),
41
+ 60 => Syscall::SyscallInfo.new(number: 60, name: :exit, arg_names: [], arg_types: []),
42
+ 61 => Syscall::SyscallInfo.new(number: 61, name: :wait4, arg_names: [], arg_types: []),
43
+ 62 => Syscall::SyscallInfo.new(number: 62, name: :kill, arg_names: [], arg_types: []),
44
+ 72 => Syscall::SyscallInfo.new(number: 72, name: :fcntl, arg_names: [], arg_types: []),
45
+ 73 => Syscall::SyscallInfo.new(number: 73, name: :flock, arg_names: [], arg_types: []),
46
+ 74 => Syscall::SyscallInfo.new(number: 74, name: :fsync, arg_names: [], arg_types: []),
47
+ 231 => Syscall::SyscallInfo.new(number: 231, name: :exit_group, arg_names: [], arg_types: []),
48
+ 257 => Syscall::SyscallInfo.new(number: 257, name: :openat, arg_names: [], arg_types: []),
49
+ 262 => Syscall::SyscallInfo.new(number: 262, name: :newfstatat, arg_names: [], arg_types: []),
50
+ 267 => Syscall::SyscallInfo.new(number: 267, name: :readlinkat, arg_names: [], arg_types: []),
51
+ 269 => Syscall::SyscallInfo.new(number: 269, name: :faccessat, arg_names: [], arg_types: [])
52
+ }.freeze
53
+
54
+ # @return [Hash{Symbol => Rptrace::Syscall::SyscallInfo}]
55
+ BY_NAME = TABLE.each_with_object({}) do |(_number, info), map|
56
+ map[info.name] = info
57
+ end.freeze
58
+ end
59
+ end
60
+ end