lldb 0.2.0 → 0.3.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -4
  3. data/README.md +86 -2
  4. data/Rakefile +36 -0
  5. data/bindings/constants.yml +137 -0
  6. data/bindings/surface.yml +3072 -0
  7. data/ext/lldb/discovery.rb +101 -0
  8. data/ext/lldb/extconf.rb +206 -88
  9. data/ext/lldb/lldb_wrapper.cpp +7934 -877
  10. data/ext/lldb/lldb_wrapper.h +596 -333
  11. data/lib/lldb/address.rb +81 -0
  12. data/lib/lldb/api_support.rb +23 -6
  13. data/lib/lldb/attach_info.rb +122 -0
  14. data/lib/lldb/block.rb +121 -0
  15. data/lib/lldb/breakpoint.rb +11 -6
  16. data/lib/lldb/breakpoint_location.rb +20 -5
  17. data/lib/lldb/broadcaster.rb +79 -0
  18. data/lib/lldb/command_interpreter.rb +9 -4
  19. data/lib/lldb/command_return_object.rb +24 -5
  20. data/lib/lldb/compile_unit.rb +64 -0
  21. data/lib/lldb/context.rb +81 -0
  22. data/lib/lldb/debugger.rb +55 -12
  23. data/lib/lldb/error.rb +50 -3
  24. data/lib/lldb/event.rb +109 -0
  25. data/lib/lldb/expression_options.rb +124 -0
  26. data/lib/lldb/ffi_bindings.rb +313 -31
  27. data/lib/lldb/file_spec.rb +90 -0
  28. data/lib/lldb/file_spec_list.rb +78 -0
  29. data/lib/lldb/frame.rb +109 -17
  30. data/lib/lldb/function.rb +121 -0
  31. data/lib/lldb/instruction.rb +83 -0
  32. data/lib/lldb/instruction_list.rb +68 -0
  33. data/lib/lldb/launch_info.rb +145 -19
  34. data/lib/lldb/line_entry.rb +75 -0
  35. data/lib/lldb/listener.rb +96 -0
  36. data/lib/lldb/memory_region_info.rb +8 -3
  37. data/lib/lldb/module.rb +46 -5
  38. data/lib/lldb/native.rb +36 -0
  39. data/lib/lldb/native_buffer.rb +32 -0
  40. data/lib/lldb/native_handle.rb +60 -0
  41. data/lib/lldb/native_lifecycle.rb +64 -0
  42. data/lib/lldb/native_string_array.rb +34 -0
  43. data/lib/lldb/process.rb +102 -40
  44. data/lib/lldb/symbol.rb +117 -0
  45. data/lib/lldb/symbol_context.rb +9 -4
  46. data/lib/lldb/target.rb +69 -67
  47. data/lib/lldb/thread.rb +51 -18
  48. data/lib/lldb/type.rb +40 -3
  49. data/lib/lldb/type_member.rb +67 -0
  50. data/lib/lldb/types.rb +69 -4
  51. data/lib/lldb/value.rb +27 -17
  52. data/lib/lldb/value_list.rb +9 -5
  53. data/lib/lldb/version.rb +1 -1
  54. data/lib/lldb/watchpoint.rb +26 -9
  55. data/lib/lldb.rb +78 -10
  56. data/lldb.gemspec +4 -1
  57. data/rbs_collection.yaml +1 -1
  58. data/script/check_bindings +259 -0
  59. data/script/guard_c_exports +139 -0
  60. data/sig/lldb/ffi_bindings.rbs +247 -21
  61. data/sig/lldb/weak_ref.rbs +7 -0
  62. metadata +30 -6
  63. data/ext/lldb/Makefile +0 -24
  64. data/ext/lldb/liblldb_wrapper.dylib +0 -0
  65. data/ext/lldb/lldb_wrapper.o +0 -0
  66. data/ext/lldb/mkmf.log +0 -24
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rbconfig'
4
+
5
+ module LLDB
6
+ module BuildDiscovery
7
+ Candidate = Struct.new(:include_dir, :lib_dir, :llvm_config, keyword_init: true)
8
+
9
+ DEFAULT_PREFIXES = [
10
+ '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr',
11
+ '/Library/Developer/CommandLineTools/usr',
12
+ '/opt/homebrew/opt/llvm',
13
+ '/usr/local/opt/llvm',
14
+ '/usr',
15
+ '/usr/local'
16
+ ].freeze
17
+
18
+ module_function
19
+
20
+ def candidates(options = {}, env: ENV, path: ENV.fetch('PATH', ''))
21
+ explicit_include = options[:include] || options[:include_dir]
22
+ explicit_lib = options[:lib] || options[:lib_dir]
23
+ explicit_dir = options[:dir] || options[:lldb_dir] || env['LLDB_DIR']
24
+
25
+ candidates = []
26
+ candidates << Candidate.new(include_dir: explicit_include, lib_dir: explicit_lib) if explicit_include || explicit_lib
27
+
28
+ if explicit_dir
29
+ candidates << candidate_for_prefix(explicit_dir)
30
+ end
31
+
32
+ llvm_config_paths(path).each do |llvm_config|
33
+ candidates << candidate_for_llvm_config(llvm_config)
34
+ end
35
+
36
+ prefix_paths(explicit_dir, env: env).each do |prefix|
37
+ candidates << candidate_for_prefix(prefix)
38
+ end
39
+
40
+ candidates.compact.uniq { |candidate| [candidate.include_dir, candidate.lib_dir] }
41
+ end
42
+
43
+ def candidate_for_llvm_config(llvm_config)
44
+ include_dir = command_output(llvm_config, '--includedir')
45
+ lib_dir = command_output(llvm_config, '--libdir')
46
+ return unless include_dir && lib_dir
47
+
48
+ Candidate.new(include_dir: include_dir, lib_dir: lib_dir, llvm_config: llvm_config)
49
+ end
50
+
51
+ def candidate_for_prefix(prefix)
52
+ return unless prefix
53
+
54
+ include_dir = File.join(prefix, 'include')
55
+ lib_dir = %w[lib lib64].map { |name| File.join(prefix, name) }.find do |path|
56
+ File.directory?(path)
57
+ end
58
+
59
+ Candidate.new(include_dir: include_dir, lib_dir: lib_dir)
60
+ end
61
+
62
+ def prefix_paths(explicit_dir, env: ENV)
63
+ paths = []
64
+ paths << explicit_dir if explicit_dir
65
+ paths << env['LLVM_PREFIX'] if env['LLVM_PREFIX']
66
+ paths.concat(DEFAULT_PREFIXES)
67
+ paths.concat(Dir.glob('/usr/lib/llvm-*').sort_by { |path| version_key(path) }.reverse)
68
+ paths.compact.uniq
69
+ end
70
+
71
+ def llvm_config_paths(path)
72
+ executables = path.split(File::PATH_SEPARATOR).flat_map do |directory|
73
+ next [] unless File.directory?(directory)
74
+
75
+ Dir.children(directory).filter_map do |name|
76
+ next unless name.match?(/\Allvm-config(?:-\d+(?:\.\d+)*)?\z/)
77
+
78
+ executable = File.join(directory, name)
79
+ executable if File.executable?(executable)
80
+ end
81
+ end
82
+
83
+ unique_executables = executables.uniq
84
+ unversioned = unique_executables.select { |path| File.basename(path) == 'llvm-config' }
85
+ versioned = unique_executables.reject { |path| File.basename(path) == 'llvm-config' }
86
+
87
+ unversioned + versioned.sort_by { |path| version_key(path) }.reverse
88
+ end
89
+
90
+ def command_output(*command)
91
+ output = IO.popen(command, &:read).strip
92
+ output.empty? ? nil : output
93
+ rescue SystemCallError
94
+ nil
95
+ end
96
+
97
+ def version_key(value)
98
+ value.to_s.scan(/\d+/).map(&:to_i)
99
+ end
100
+ end
101
+ end
data/ext/lldb/extconf.rb CHANGED
@@ -2,124 +2,241 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'mkmf'
5
+ require 'open3'
6
+ require 'rbconfig'
7
+ require 'shellwords'
8
+ require 'tmpdir'
5
9
 
6
- # Enable C++17
7
- $CXXFLAGS ||= ''
8
- $CXXFLAGS << ' -std=c++17'
10
+ require_relative 'discovery'
9
11
 
10
- # Common LLDB search paths
11
- lldb_search_paths = [
12
- # macOS Xcode
13
- '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr',
14
- '/Library/Developer/CommandLineTools/usr',
15
- # Homebrew LLVM on macOS (Apple Silicon)
16
- '/opt/homebrew/opt/llvm',
17
- # Homebrew LLVM on macOS (Intel)
18
- '/usr/local/opt/llvm',
19
- # Linux system paths
20
- '/usr',
21
- '/usr/local'
22
- ]
23
-
24
- # Find LLDB version-specific paths on Linux
25
- Dir.glob('/usr/lib/llvm-*').each do |path|
26
- lldb_search_paths << path
12
+ unless RbConfig::CONFIG['host_os'] =~ /darwin|linux/
13
+ abort "Unsupported platform: #{RbConfig::CONFIG['host_os']} (supported: Linux, macOS)"
27
14
  end
28
15
 
29
- lldb_lib_dir = nil
30
- lldb_include_dir = nil
16
+ $CXXFLAGS ||= ''
17
+ $CXXFLAGS << ' -std=c++17'
31
18
 
32
- # Try to find LLDB using lldb-config or llvm-config
33
- llvm_config = find_executable('llvm-config') ||
34
- find_executable('llvm-config-18') ||
35
- find_executable('llvm-config-17') ||
36
- find_executable('llvm-config-16') ||
37
- find_executable('llvm-config-15') ||
38
- find_executable('llvm-config-14')
19
+ def run_command(*command)
20
+ stdout, stderr, status = Open3.capture3(*command)
21
+ [stdout, stderr, status]
22
+ end
39
23
 
40
- if llvm_config
41
- lldb_lib_dir = `#{llvm_config} --libdir`.strip
42
- lldb_include_dir = `#{llvm_config} --includedir`.strip
43
- else
44
- # Search manually
45
- lldb_search_paths.each do |base_path|
46
- next unless File.directory?(base_path)
24
+ def native_probe(compiler, include_dir, lib_dir, source, link:)
25
+ Dir.mktmpdir('lldb-ruby-probe') do |directory|
26
+ source_path = File.join(directory, 'probe.cpp')
27
+ output_path = File.join(directory, 'probe')
28
+ File.write(source_path, source)
29
+
30
+ command = [*compiler, '-std=c++17', '-I', include_dir]
31
+ if link
32
+ command.concat([source_path, '-L', lib_dir, '-llldb', '-o', output_path])
33
+ command << "-Wl,-rpath,#{lib_dir}"
34
+ command << (RbConfig::CONFIG['host_os'] =~ /darwin/ ? '-lc++' : '-lstdc++')
35
+ else
36
+ command.concat(['-fsyntax-only', source_path])
37
+ end
47
38
 
48
- lib_candidates = [
49
- File.join(base_path, 'lib'),
50
- File.join(base_path, 'lib64')
51
- ]
39
+ run_command(*command)
40
+ end
41
+ end
52
42
 
53
- lib_candidates.each do |lib_path|
54
- next unless File.directory?(lib_path)
43
+ def lldb_library_present?(lib_dir)
44
+ return false unless lib_dir && File.directory?(lib_dir)
55
45
 
56
- # Check for liblldb
57
- has_lldb = File.exist?(File.join(lib_path, 'liblldb.so')) ||
58
- File.exist?(File.join(lib_path, 'liblldb.dylib')) ||
59
- Dir.glob(File.join(lib_path, 'liblldb.so.*')).any?
46
+ Dir.glob(File.join(lib_dir, 'liblldb.{so,dylib}*')).any?
47
+ end
60
48
 
61
- next unless has_lldb
49
+ def lldb_probe(compiler, candidate)
50
+ return [false, 'include directory does not exist'] unless File.directory?(candidate.include_dir)
51
+ return [false, 'library directory does not contain liblldb'] unless lldb_library_present?(candidate.lib_dir)
52
+
53
+ source = <<~CPP
54
+ #include <lldb/API/LLDB.h>
55
+
56
+ int main() {
57
+ lldb::SBDebugger debugger;
58
+ return debugger.IsValid() ? 0 : 0;
59
+ }
60
+ CPP
61
+
62
+ stdout, stderr, status = native_probe(
63
+ compiler,
64
+ candidate.include_dir,
65
+ candidate.lib_dir,
66
+ source,
67
+ link: true
68
+ )
69
+ return [true, nil] if status.success?
70
+
71
+ [false, [stdout, stderr].reject(&:empty?).join("\n")]
72
+ end
62
73
 
63
- lldb_lib_dir = lib_path
64
- lldb_include_dir = File.join(base_path, 'include')
65
- break
66
- end
74
+ def watchpoint_capability_probe(compiler, candidate)
75
+ source = <<~CPP
76
+ #include <lldb/API/SBWatchpoint.h>
77
+
78
+ int main() {
79
+ auto reads = static_cast<bool (lldb::SBWatchpoint::*)()>(
80
+ &lldb::SBWatchpoint::IsWatchingReads);
81
+ auto writes = static_cast<bool (lldb::SBWatchpoint::*)()>(
82
+ &lldb::SBWatchpoint::IsWatchingWrites);
83
+ (void)reads;
84
+ (void)writes;
85
+ return 0;
86
+ }
87
+ CPP
88
+
89
+ _stdout, _stderr, status = native_probe(
90
+ compiler,
91
+ candidate.include_dir,
92
+ candidate.lib_dir,
93
+ source,
94
+ link: false
95
+ )
96
+ status.success?
97
+ end
67
98
 
68
- break if lldb_lib_dir
69
- end
99
+ def api_capability_probe(compiler, candidate, header, expression)
100
+ source = <<~CPP
101
+ #include <lldb/API/#{header}>
102
+
103
+ int main() {
104
+ auto method = #{expression};
105
+ (void)method;
106
+ return 0;
107
+ }
108
+ CPP
109
+
110
+ _stdout, _stderr, status = native_probe(
111
+ compiler,
112
+ candidate.include_dir,
113
+ candidate.lib_dir,
114
+ source,
115
+ link: false
116
+ )
117
+ status.success?
70
118
  end
71
119
 
120
+ def llvm_config_version(candidate)
121
+ llvm_config = candidate.llvm_config
122
+ if !llvm_config
123
+ possible = File.join(candidate.lib_dir.to_s, '..', 'bin', 'llvm-config')
124
+ llvm_config = possible if File.executable?(possible)
125
+ end
126
+ return 'unknown' unless llvm_config
72
127
 
73
- unless lldb_lib_dir
74
- abort <<~MSG
128
+ stdout, _stderr, status = run_command(llvm_config, '--version')
129
+ status.success? && !stdout.strip.empty? ? stdout.strip : 'unknown'
130
+ end
75
131
 
76
- *** ERROR: Could not find LLDB library ***
132
+ explicit_options = {
133
+ dir: with_config('lldb-dir'),
134
+ include: with_config('lldb-include'),
135
+ lib: with_config('lldb-lib')
136
+ }.compact
137
+
138
+ compiler = Shellwords.split(ENV.fetch('CXX', RbConfig::CONFIG['CXX'] || 'c++'))
139
+ abort 'C++ compiler command is empty' if compiler.empty?
140
+ attempts = []
141
+ selected = nil
142
+
143
+ LLDB::BuildDiscovery.candidates(explicit_options).each do |candidate|
144
+ success, reason = lldb_probe(compiler, candidate)
145
+ unless success
146
+ attempts << "#{candidate.inspect}: #{reason}"
147
+ next
148
+ end
77
149
 
78
- Please install LLDB development files:
150
+ selected = candidate
151
+ break
152
+ end
79
153
 
80
- On Ubuntu/Debian:
81
- sudo apt-get install lldb-14 liblldb-14-dev
154
+ unless selected
155
+ abort <<~MSG
82
156
 
83
- On Fedora/RHEL:
84
- sudo dnf install lldb-devel
157
+ *** ERROR: Could not find a usable LLDB installation ***
85
158
 
86
- On macOS:
87
- xcode-select --install
88
- # or
89
- brew install llvm
159
+ A C++17 program including lldb/API/LLDB.h and linking against liblldb
160
+ must compile successfully. Tried:
161
+ #{attempts.join("\n")}
90
162
 
91
- You can also set LLDB_DIR environment variable to point to your LLDB installation:
92
- LLDB_DIR=/path/to/llvm gem install lldb
163
+ Supported inputs, in priority order:
164
+ --with-lldb-include=/path/to/include --with-lldb-lib=/path/to/lib
165
+ --with-lldb-dir=/path/to/llvm
166
+ LLDB_DIR=/path/to/llvm
167
+ llvm-config or llvm-config-N on PATH
93
168
 
169
+ The compiler used was: #{compiler}
94
170
  MSG
95
171
  end
96
172
 
97
- puts "Found LLDB library directory: #{lldb_lib_dir}"
98
- puts "Found LLDB include directory: #{lldb_include_dir}" if lldb_include_dir
99
-
100
- # Add include paths
101
- $CXXFLAGS << " -I#{lldb_include_dir}" if lldb_include_dir && File.directory?(lldb_include_dir)
102
-
103
- # Link against LLDB library
104
- $LDFLAGS << " -L#{lldb_lib_dir} -llldb"
105
- $LDFLAGS << " -Wl,-rpath,#{lldb_lib_dir}"
106
-
107
- # Check for C++ standard library
108
- $LDFLAGS << if RUBY_PLATFORM =~ /darwin/
173
+ puts "Found LLDB library directory: #{selected.lib_dir}"
174
+ puts "Found LLDB include directory: #{selected.include_dir}"
175
+ puts "Found LLDB via: #{selected.llvm_config || 'prefix discovery'}"
176
+ puts "Using C++ compiler: #{compiler.join(' ')}"
177
+
178
+ watchpoint_access_kind = watchpoint_capability_probe(compiler, selected)
179
+ symbol_get_base_name = api_capability_probe(
180
+ compiler, selected, 'SBSymbol.h', '&lldb::SBSymbol::GetBaseName'
181
+ )
182
+ symbol_get_id = api_capability_probe(
183
+ compiler, selected, 'SBSymbol.h', '&lldb::SBSymbol::GetID'
184
+ )
185
+ symbol_get_value = api_capability_probe(
186
+ compiler, selected, 'SBSymbol.h', '&lldb::SBSymbol::GetValue'
187
+ )
188
+ symbol_get_size = api_capability_probe(
189
+ compiler, selected, 'SBSymbol.h', '&lldb::SBSymbol::GetSize'
190
+ )
191
+ function_get_base_name = api_capability_probe(
192
+ compiler, selected, 'SBFunction.h', '&lldb::SBFunction::GetBaseName'
193
+ )
194
+ basic_type_char8 = api_capability_probe(
195
+ compiler, selected, 'SBType.h', 'static_cast<int>(lldb::eBasicTypeChar8)'
196
+ )
197
+ build_version = llvm_config_version(selected)
198
+ config_path = File.expand_path('lldb_wrapper_config.h', __dir__)
199
+ File.write(config_path, <<~HEADER)
200
+ #ifndef LLDB_WRAPPER_CONFIG_H
201
+ #define LLDB_WRAPPER_CONFIG_H
202
+
203
+ #define LLDB_RUBY_WRAPPER_ABI_VERSION 1
204
+ #define LLDB_RUBY_BUILD_LLDB_VERSION #{build_version.dump}
205
+ #define LLDB_RUBY_HAVE_WATCHPOINT_ACCESS_KIND #{watchpoint_access_kind ? 1 : 0}
206
+ #define LLDB_RUBY_HAVE_SYMBOL_GET_BASE_NAME #{symbol_get_base_name ? 1 : 0}
207
+ #define LLDB_RUBY_HAVE_SYMBOL_GET_ID #{symbol_get_id ? 1 : 0}
208
+ #define LLDB_RUBY_HAVE_SYMBOL_GET_VALUE #{symbol_get_value ? 1 : 0}
209
+ #define LLDB_RUBY_HAVE_SYMBOL_GET_SIZE #{symbol_get_size ? 1 : 0}
210
+ #define LLDB_RUBY_HAVE_FUNCTION_GET_BASE_NAME #{function_get_base_name ? 1 : 0}
211
+ #define LLDB_RUBY_HAVE_BASIC_TYPE_CHAR8 #{basic_type_char8 ? 1 : 0}
212
+
213
+ #endif
214
+ HEADER
215
+ puts "Watchpoint access capability: #{watchpoint_access_kind ? 'supported' : 'unsupported'}"
216
+ puts "SBSymbol::GetBaseName capability: #{symbol_get_base_name ? 'supported' : 'unsupported'}"
217
+ puts "SBSymbol::GetID capability: #{symbol_get_id ? 'supported' : 'unsupported'}"
218
+ puts "SBSymbol::GetValue capability: #{symbol_get_value ? 'supported' : 'unsupported'}"
219
+ puts "SBSymbol::GetSize capability: #{symbol_get_size ? 'supported' : 'unsupported'}"
220
+ puts "SBFunction::GetBaseName capability: #{function_get_base_name ? 'supported' : 'unsupported'}"
221
+ puts "BasicType::Char8 capability: #{basic_type_char8 ? 'supported' : 'unsupported'}"
222
+ puts "Build LLDB version: #{build_version}"
223
+
224
+ $CXXFLAGS << " -I#{selected.include_dir}"
225
+ $LDFLAGS << " -L#{selected.lib_dir} -llldb"
226
+ $LDFLAGS << " -Wl,-rpath,#{selected.lib_dir}"
227
+ $LDFLAGS << if RbConfig::CONFIG['host_os'] =~ /darwin/
109
228
  ' -lc++'
110
229
  else
111
230
  ' -lstdc++'
112
231
  end
113
232
 
114
- # Create the extension as a shared library, not a Ruby native extension
115
- # because we're using FFI to load it
233
+ # Create the extension as a shared library, not a Ruby native extension,
234
+ # because the Ruby layer loads it through FFI.
116
235
  $srcs = ['lldb_wrapper.cpp']
117
236
  $objs = ['lldb_wrapper.o']
118
-
119
- # Set the target library name
120
237
  $DLDFLAGS ||= ''
121
238
 
122
- if RUBY_PLATFORM =~ /darwin/
239
+ if RbConfig::CONFIG['host_os'] =~ /darwin/
123
240
  target = 'liblldb_wrapper.dylib'
124
241
  $DLDFLAGS << ' -dynamiclib'
125
242
  else
@@ -127,7 +244,6 @@ else
127
244
  $DLDFLAGS << ' -shared'
128
245
  end
129
246
 
130
- # Create a custom Makefile that builds a shared library
131
247
  File.open('Makefile', 'w') do |f|
132
248
  f.puts <<~MAKEFILE
133
249
  CXX = #{RbConfig::CONFIG['CXX'] || 'c++'}
@@ -137,6 +253,8 @@ File.open('Makefile', 'w') do |f|
137
253
  TARGET = #{target}
138
254
  SRCS = lldb_wrapper.cpp
139
255
  OBJS = lldb_wrapper.o
256
+ sitelibdir ?= #{RbConfig::CONFIG['sitelibdir']}
257
+ sitearchdir ?= #{RbConfig::CONFIG['sitearchdir']}
140
258
 
141
259
  all: $(TARGET)
142
260
 
@@ -147,13 +265,13 @@ File.open('Makefile', 'w') do |f|
147
265
  \t$(CXX) $(CXXFLAGS) -c -o $@ $<
148
266
 
149
267
  install: $(TARGET)
150
- \tmkdir -p $(DESTDIR)#{RbConfig::CONFIG['sitelibdir']}/lldb
151
- \tcp $(TARGET) $(DESTDIR)#{RbConfig::CONFIG['sitelibdir']}/lldb/
268
+ \tmkdir -p $(DESTDIR)$(sitearchdir)/lldb
269
+ \tcp $(TARGET) $(DESTDIR)$(sitearchdir)/lldb/
152
270
 
153
271
  clean:
154
272
  \trm -f $(OBJS) $(TARGET)
155
273
 
156
- .PHONY: all install clean
274
+ .PHONY: all clean install
157
275
  MAKEFILE
158
276
  end
159
277