[Tutoriel] [Ruby] Création et débogage de gemmes d'extension native C

English

Un tutoriel sur la création d'un bijou Ruby avec des extensions natives C et le débogage avec gdb.

Le code utilisé dans cet article a été téléchargé sur GitHub.

Construire CRuby

Cette étape n'est pas obligatoire, vous pouvez donc la sauter.

En construisant ruby, Ruby lui-même peut être progressé pendant le développement de débogage de gemmes, ce qui facilite le développement de gemmes.

Ici, utilisez rbenv + ruby-build. Supposons que rbenv soit installé dans ~ / .rbenv.

# --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

Sur macOS [problème de conception de code](https://www.google.com/search?safe=off&ei=ijNmXN3jB8aq8QXvvpfgDA&q=please+check+gdb+is+codesigned+-+see+taskgated%288%29&oq=please+check+gdb+ est + codé + - + voir + 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), utilisez donc lldb au lieu de gdb ou GDB Wiki Description ) Et effectuez la signature du code.

La désactivation de l'optimisation (-O0) facilite le débogage:

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

A partir de maintenant, j'utiliserai rbenv shell trunk (~ / .rbenv / versions / trunk /).

Créer et construire un bijou

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

Une erreur se produira comme indiqué ci-dessous.

$ 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."'

Modifiez et corrigez ʻ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

Cela devrait réussir la construction.

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

Implémentons l'extension C.

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

L'API Ruby C est rarement documentée. Le Guide définitif de l'API C de Ruby (http://silverhammermba.github.io/emberb/c/) est recommandé comme premier guide à lire.

Débogage des extensions C avec gdb

Construisez l'extension C avec l'optimisation désactivée (-O0) et avec les symboles de débogage ( -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 est équivalent à RbConfig :: MAKEFILE_CONFIG, qui stocke la configuration de construction de ruby. Par conséquent, la différence ci-dessus peut ne pas être nécessaire, mais assurez-vous que make V = 1 est -O0 -ggdb3.

Dans bear et intercept-build, [compilation database (compile_commands.json)]( Lors de la création (https://clang.llvm.org/docs/JSONCompilationDatabase.html), l'analyse statique peut être effectuée comme suit.

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

Essayez de déboguer avec 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): Dans bundle exec,require "example_ext"(indirectement) lit / path / to / example_ext / example_ext.so (ʻexample_ext.bundle sur macOS). ʻLes informations de débogage pour example_ext.so se réfèrent à / chemin / vers / exemple_ext / ext / exemple_ext / exemple_ext.c. Dans (b), vous devez spécifier directement l'emplacement du binaire ruby. C'est parce que sans bundle exec, la commande ruby pointe vers un script shell et ne peut pas être lue par gdb. Vous devez également spécifier RUBY_LIB = ./ lib. Sans lui (c), la bibliothèque partagée chargée serait ~ / .rbenv / versions / trunk /.../ example_ext.so et debug .rbenv / versions / trunk /.../ example_ext.c. Sera fait [^ 1]. Même dans ce cas, vous pouvez vous référer à la source d'origine avec gdb set substitute-path.

Pour plus de détails, essayez d'exécuter la commande suivante.

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 $:'

référence

Licence

<img alt = "Creative Commons License" style = "border-width: 0" src = "https" //i.creativecommons.org/l/by/4.0/88x31.png "/>
Ce travail est <a rel =" license "href =" http://creativecommons.org/ Licences / par / 4.0 / "> Licence internationale Creative Commons Attribution 4.0 .

Pour les responsables de la documentation officielle des logiciels associés: Si vous souhaitez inclure le contenu de cet article dans la documentation officielle, veuillez commenter cet article ou twitter @wata_ash ) Contactez nous s'il vous plait.

[^ 1]: La tâche rake install exécute la tâche build, qui exécute gem install / path / to / example_ext / pkg / example_ext-0.1.0.gem. gem install example_ext-0.1.0.gem étend la source C à ~ / .rbenv / versions / trunk / lib / ruby / gems / 2.7.0 / gems / example_ext-0.1.0 / puis compile Par conséquent, le chemin source des informations de débogage pointera vers ~ / .rbenv / versions / trunk / lib / ruby / gems / 2.7.0 / gems / example_ext-0.1.0 / ext / example_ext / example_ext.c. Je vais finir. Ceci n'est pas documenté, mais la source (1) (2) [(3)](https://github.com/rubygems/rubygems/blob/v3. 0.2 / lib / rubygems / installer.rb # L318) (4) Et bundle exec ruby -rtracer ~ / .rbenv / versions / trunk / bin / rake --trace install.

Recommended Posts

[Tutoriel] [Ruby] Création et débogage de gemmes d'extension native C
Extension Ruby C et volatile
Rubis et gemme
J'ai essayé d'implémenter Ruby avec Ruby (et C) (j'ai joué avec intégré)
J'ai créé une bibliothèque d'extension Ruby en C