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.
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
debug = flags = -ggdb3
est la valeur par défaut, vous n'avez donc pas besoin de spécifier les options de débogage.
--MacOS / usr / lib / libcrypto.dylib
est trop ancien pour construire le module OpenSSL, donc après brew install openssl
, ajoutez CPPFLAGS = "- I / usr / local / opt / openssl
pour configure
. Ajoutez / incluez "LDFL AGS =" - L / usr / local / opt / openssl / lib "`.V = 1
.A partir de maintenant, j'utiliserai rbenv shell trunk
(~ / .rbenv / versions / trunk /
).
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."
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);
}
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.
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")
$ 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 $:'
<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
.