[Tutorial] [Ruby] Erstellen und Debuggen von C-nativen Erweiterungsedelsteinen

English

Ein Tutorial zum Erstellen eines Ruby-Edelsteins mit nativen C-Erweiterungen und zum Debuggen mit gdb.

Der in diesem Artikel verwendete Code wurde in GitHub hochgeladen.

Bauen Sie CRuby

Dieser Schritt ist nicht obligatorisch, Sie können ihn also überspringen.

Durch das Erstellen von "Ruby" können Sie auch Ruby selbst während der Entwicklung des Gem-Debugs durchlaufen, was die Entwicklung von Gem unterstützt.

Verwenden Sie hier rbenv + ruby-build. Angenommen, rbenv ist in ~ / .rbenv installiert.

# --keep keeps source code in ~/.rbenv/sources/2.6.1/ruby-2.6.1/
rbenv install --keep --verbose 2.6.1
rbenv shell 2.6.1

# check that ruby is debuggable
type ruby           # => ruby is /home/wsh/.rbenv/shims/ruby
rbenv which ruby    # => /home/wsh/.rbenv/versions/2.6.1/bin/ruby
gdb -q ~/.rbenv/versions/2.6.1/bin/ruby
# (gdb) break main
# (gdb) run
# Breakpoint 1, main (argc=1, argv=0x7fffffffdd58) at ./main.c:30
# 30  {
# (gdb) list
# 25  #include <stdlib.h>
# 26  #endif
# 27  
# 28  int
# 29  main(int argc, char **argv)
# 30  {
# 31  #ifdef RUBY_DEBUG_ENV
# 32      ruby_set_debug_option(getenv("RUBY_DEBUG"));
# 33  #endif
# 34  #ifdef HAVE_LOCALE_H

Unter macOS [Codezeichenproblem](https://www.google.com/search?safe=off&ei=ijNmXN3jB8aq8QXvvpfgDA&q=please+check+gdb+is+codesigned+-+see+taskgated%288%29&oq=please+check+gdb+ ist + codiert + - + siehe + taskgated% 288% 29 & gs_l = psy-ab.3..0.232958.232958..233371 ... 0.0..0.92.92.1 ...... 0 .... 2j1..gws -wiz ....... 0i71.1sEziP0mz_E), verwenden Sie also lldb anstelle von gdb oder GDB Wiki Description ) Und führen Sie eine Codesignatur durch.

Das Deaktivieren der Optimierung (-O0) erleichtert das Debuggen:

git clone https://github.com/ruby/ruby.git
cd ruby/
autoconf -v  # as written in README.md
mkdir build; cd build/
# --disable-install-doc saves build time
../configure --prefix=$HOME/.rbenv/versions/trunk --disable-install-doc --enable-debug-env optflags="-O0"
make V=1 -j4
make install
rbenv shell trunk
ruby --version  # 2.?.?dev
gdb -q ~/.rbenv/versions/trunk/bin/ruby

or:

wget https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.gz
tar xvf ruby-2.6.1.tar.gz
cd ruby-2.6.1/
mkdir build/; cd build/
../configure --prefix=$HOME/.rbenv/versions/trunk --disable-install-doc --enable-debug-env optflags="-O0"
make V=1 -j4
make install
rbenv shell 2.6.1-dbg
ruby --version  # 2.6.1
gdb -q ~/.rbenv/versions/2.6.1-dbg/bin/ruby

Von nun an werde ich rbenv shell trunk (~ / .rbenv / version / trunk /) verwenden.

Erstellen und bauen Sie Edelsteine

rbenv shell trunk
cd ~/work/
bundle gem example_ext --coc --ext --mit --test
cd example_ext/
bin/setup  # as described in ./README.md

Ein Fehler tritt wie unten gezeigt auf.

$ bin/setup

bundle install
+ bundle install
You have one or more invalid gemspecs that need to be fixed.
The gemspec at /home/wsh/work/example_ext/example_ext.gemspec is not valid. Please fix this gemspec.
The validation error was 'metadata['homepage_uri'] has invalid link: "TODO: Put your gem's website or public repo URL here."'

Bearbeiten und korrigieren Sie example_ext.gemspec:

diff --git a/example_ext.gemspec b/example_ext.gemspec
index 4b9d3c1..1446707 100644
--- a/example_ext.gemspec
+++ b/example_ext.gemspec
@@ -9,9 +9,9 @@ Gem::Specification.new do |spec|
   spec.authors       = ["Wataru Ashihara"]
   spec.email         = ["[email protected]"]
 
-  spec.summary       = %q{TODO: Write a short summary, because RubyGems requires one.}
-  spec.description   = %q{TODO: Write a longer description or delete this line.}
-  spec.homepage      = "TODO: Put your gem's website or public repo URL here."
+  spec.summary       = %q{Write a short summary, because RubyGems requires one.}
+  spec.description   = %q{Write a longer description or delete this line.}
+  # spec.homepage      = "TODO: Put your gem's website or public repo URL here."
   spec.license       = "MIT"
 
   # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
   if spec.respond_to?(:metadata)
     spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
 
-    spec.metadata["homepage_uri"] = spec.homepage
-    spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
-    spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
+    # spec.metadata["homepage_uri"] = spec.homepage
+    # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
+    # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
   else
     raise "RubyGems 2.0 or newer is required to protect against " \
       "public gem pushes."

View on GitHub

Dies sollte den Build bestehen.

bin/setup
bundle exec rake install  # as described in README.md
bundle exec ruby -e 'require "example_ext"; p ExampleExt::VERSION'
# => "0.1.0"

Lassen Sie uns die C-Erweiterung implementieren.

diff --git a/ext/example_ext/example_ext.c b/ext/example_ext/example_ext.c
index c89d90a..f47c72e 100644
--- a/ext/example_ext/example_ext.c
+++ b/ext/example_ext/example_ext.c
@@ -2,8 +2,18 @@
 
 VALUE rb_mExampleExt;
 
+static VALUE
+example_hello(int argc, VALUE *argv)
+{
+  printf("hello\n");
+
+  return Qnil;
+}
+
 void
 Init_example_ext(void)
 {
   rb_mExampleExt = rb_define_module("ExampleExt");
+
+  rb_define_module_function(rb_mExampleExt, "hello", example_hello, -1);
 }

View on GitHub

bin/setup
bundle exec rake install
bundle exec ruby -e 'require "example_ext"; ExampleExt::hello'
# => hello

Die Ruby C-API wird selten dokumentiert. Der endgültige Leitfaden zur Ruby-C-API (http://silverhammermba.github.io/emberb/c/) wird als erster zu lesender Leitfaden empfohlen.

Debuggen von C-Erweiterungen mit gdb

Erstellen Sie die C-Erweiterung mit deaktivierter Optimierung (-O0) und mit Debug-Symbolen ( -ggdb3).

cd ext/example_ext/
vim extconf.rb
ruby extconf.rb # specify -ggdb3 -O0
make V=1        # check -ggdb3 -O0
make clean
cd ../../
bundle exec rake install
diff --git a/ext/example_ext/extconf.rb b/ext/example_ext/extconf.rb
index f657c82..2ca74f1 100644
--- a/ext/example_ext/extconf.rb
+++ b/ext/example_ext/extconf.rb
@@ -1,3 +1,6 @@
 require "mkmf"
 
+CONFIG["debugflags"] = "-ggdb3"
+CONFIG["optflags"] = "-O0"
+
 create_makefile("example_ext/example_ext")

View on GitHub

$ make V=1
gcc -I. ... -O0 -ggdb3 ... -o example_ext.o -c example_ext.c
...

COINFIG entspricht RbConfig :: MAKEFILE_CONFIG, in dem die Build-Konfiguration von ruby gespeichert ist. Daher ist der obige Unterschied möglicherweise nicht erforderlich, aber stellen Sie sicher, dass "make V = 1" "-O0 -ggdb3" ist.

[Kompilierungsdatenbank (compile_commands.json)](in bear und intercept-build Beim Erstellen (https://clang.llvm.org/docs/JSONCompilationDatabase.html) kann die statische Analyse wie folgt durchgeführt werden.

bundle exec rake clean && bundle exec bear rake build
jq '.' compile_commands.json > compile_commands.json.orig
jq --arg IPWD "-I$PWD" --arg IRUBY \
  "-I$HOME/src/ruby/include" '.[].arguments |= [ .[0], $IPWD, $IRUBY, .[1:][] ]' \
  compile_commands.json.orig > compile_commands.json

Versuchen Sie das Debuggen mit gdb.

# (a) recommended:
bundle exec \
  gdb -q -ex 'set breakpoint pending on' -ex 'b example_hello' -ex run --args ruby -e 'require "example_ext"; ExampleExt::hello'
# (b) or:
env RUBYLIB=./lib \
  gdb -q -ex 'set breakpoint pending on' -ex 'b example_hello' -ex run --args ~/.rbenv/versions/trunk/bin/ruby -e 'require "example_ext"; ExampleExt::hello'
# (c) unrecommended:
gdb -q -ex 'set breakpoint pending on' -ex 'b example_hello' -ex run --args ~/.rbenv/versions/trunk/bin/ruby -e 'require "example_ext"; ExampleExt::hello'

TL; DR:

(a): In "bundle exec" erfordert "require_ext" (indirekt) "/ path / to / example_ext / example_ext.so" ("example_ext.bundle" unter macOS). Die Debug-Informationen für "example_ext.so" beziehen sich auf "/ path / to / example_ext / ext / example_ext / example_ext.c". In (b) müssen Sie die Position der "Ruby" -Binärdatei direkt angeben. Dies liegt daran, dass der Befehl "ruby" ohne "bundle exec" auf ein Shell-Skript verweist und von gdb nicht gelesen werden kann. Sie müssen auch RUBY_LIB = ./ lib angeben. Ohne sie (c) wäre die geladene gemeinsam genutzte Bibliothek "~ / .rbenv / version / trunk /.../ example_ext.so" und debug ".rbenv / version / trunk /.../ example_ext.c". Wird gemacht [^ 1]. Auch in diesem Fall können Sie mit gdb set replace-path auf die Originalquelle verweisen.

Versuchen Sie für weitere Details, den folgenden Befehl auszuführen.

echo $PATH
which -a ruby
bundle exec echo $PATH
bundle exec which -a ruby
file ~/.rbenv/shims/ruby
file ~/.rbenv/versions/trunk/bin/ruby
ruby -e 'p $:'  # or $LOAD_PATH
bundle exec ruby -e 'p $:'
env RUBYLIB=./lib ruby -e 'p $:'

Referenz

Lizenz

<img alt = "Creative Commons License" style = "Rahmenbreite: 0" src = "https" //i.creativecommons.org/l/by/4.0/88x31.png "/>
Diese Arbeit ist <eine rel =" Lizenz "href =" http://creativecommons.org/ Lizenzen / von / 4.0 / "> Creative Commons Attribution 4.0 International License .

Für Betreuer der offiziellen Dokumentation für verwandte Software: Wenn Sie den Inhalt dieses Artikels in die offizielle Dokumentation aufnehmen möchten, kommentieren Sie diesen Artikel oder Twitter @wata_ash. ) Bitte kontaktieren Sie uns.

[^ 1]: Die Task "Rake-Installation" führt die Task "Build" aus, die "gem install / path / to / example_ext / pkg / example_ext-0.1.0.gem" ausführt. gem install example_ext-0.1.0.gem erweitert die C-Quelle auf ~ / .rbenv / version / trunk / lib / ruby / gems / 2.7.0 / gems / example_ext-0.1.0 / und kompiliert dann Daher verweist der Quellpfad der Debug-Informationen auf "~ / .rbenv / version / trunk / lib / ruby / gems / 2.7.0 / gems / example_ext-0.1.0 / ext / example_ext / example_ext.c". Ich werde am Ende. Dies ist nicht dokumentiert, sondern die Quelle (1) (2) [(3)](https://github.com/rubygems/rubygems/blob/v3. 0.2 / lib / rubygems / installer.rb # L318) (4) Und Bundle Exec Ruby-Rtracer ~ / .rbenv / Versionen / Trunk / Bin / Rake - Trace installieren.

Recommended Posts

[Tutorial] [Ruby] Erstellen und Debuggen von C-nativen Erweiterungsedelsteinen
Ruby C Erweiterung und flüchtig
Rubin und Edelstein
Ich habe versucht, Ruby mit Ruby (und C) zu implementieren (ich habe mit Builtin gespielt)
Ich habe eine Ruby-Erweiterungsbibliothek in C erstellt