torutkのブログ

ソフトウェア・エンジニアのブログ

Redmine 4.0(Rails 5.1)でのプラグイン作成について

redmine trunkを落としてglossary pluginが動くか試してみる(続々々) - torutkのブログにおいて、GlossaryプラグインRedmine 4.0対応に難航し暗礁に乗り上げてしまったので、別アプローチとしてゼロからGlossaryプラグインを作成する作業を5月1日より開始しています。この作業を通じてRedmineプラグイン作成に習熟できたなら、既存のGlossaryプラグインRedmine 4.0(Rails 5.1)対応の修正をすることも容易になるだろうし、あるいはゼロから作った新Glossaryプラグインを主とし、既存のデータベースのマイグレーションを用意するのもの可能になるのかと思います。

ゼロからGlossaryプラグインを作る過程を、次のWikiに絶賛記載途中です。

Redmine Glossaryプラグイン再構築 - ソフトウェアエンジニアリング - Torutk

ステップバイステップで小さなプラグインから開始しています。最初は、モデルクラス1つ、コントローラー1つ、アクションに対応するビューが少々で、プロジェクトとの関連もない、権限の設定もないシンプルなものを作っています。本日時点でやっと一通り出来てきました。次はモデルクラスを2つに増やすところです。

ゼロからの作成作業をしていて、気づいたこと、知ったことなどを時々こちらの日記に書いていきます。

国際化対応テキストの変換メソッド名

Redmineのコードでは、ビュー(.erb)でよく見かけるのが"l"(エル)メソッドです。

<%= link_to l(:button_edit) ... %>

Rails関係を調べると、RailsI18n.translateメソッドの省略(ヘルパーメソッド)でtが、I18n.localizeメソッドの省略でlが利用できます。localaize(l)は、日付・時刻などをロケールに合わせた表現に変換するものです。

Redmineでは、なぜlメソッドを使っているのかとても悩みました。tの間違いでは?でもならなぜ正常に機能しているのか不思議でした。

調べると、Redmineのlメソッドは内部でRailsI18n.tを呼び出していました。
最初RedmineのlとRailsのlと同じ名前の関数で混同していましたが、やっと腑に落ちました。それにしても紛らわしいですね。

link_to で確認ダイアログを表示

削除アイコンをクリックしたときに、安全のため(?)、確認ダイアログを表示することがあります。

以前の書き方は、

<%= link_to l(:button_delete), hello_path, :confirm => l(:text_are_you_sure),
 :method => :delete, :class => 'icon icon-del') %>

のように、confirmをキーとするハッシュを引数に指定していました。

Rails 4.1でこのconfirmをキーとしたハッシュは廃止(削除)となっており、代替記述として次のようにdataをキーとしたハッシュでその中にconfirmをキーとしたハッシュを指定します。

<%= link_to l(:button_delete), hello_path, method: :delete,
 data: {confirm: l(:text_are_you_sure)}, class: 'icon icon-del' %>

ハッシュの書き方は、=> を使う記法と、:を使う記法があり、後者はruby 1.9で導入されたものです。Redmine 2.xはRails 3ベースでruby 1.8をサポートする必要があり、=>を使う記法でした。Redmine 3.xでRails 4.2ベースになった際、Rails 4.2がruby 1.9.3以降のサポートとなったので、Redmine 3.x以降を対象にするならハッシュの書き方に:を使う記法を使用できるようになります。

Gitリポジトリに空のブランチを作る

動機

Githubに、Redmineの用語集プラグインredmine_glossary)をフォークして修正を加えたものを置いています。
用語集プラグイン(glossary)をRedmine 3.0で動くようにしてみる - torutkの日記

Redmineの次回メジャーバージョンアップ版となる4.0.0では、ベースとなるRuby on Railsのバージョンが4.2から5.1に更新されます。これに伴い、プラグインに含まれるRails 4以前のDeprecatedとなり削除された機能を利用しているコードは修正を余儀なくされます。

そこで、redmine_glossaryのコードを再構築(リファクタリング程度の修正ではなく)しようと思います。再構築にあたっては、いったん既存コードは空にして、小さいコードから出発していきたいところです。ただし、Gitリポジトリは既存のものを使い続けたいとします*1

ふつうにgitのブランチを作ると、一番遡っても初期コミットの状態のブランチ(初期コミットで追加されたファイル群が存在する)となってしまいます。最初が空ならよかったのですが、フォークしてきたリポジトリは初期コミットがglossary_pluginのとあるバージョンのもの全体となっています。

そこで、空の新規ブランチを作れないか?というのが動機です。

捜索

検索ワード[git ブランチ 空]でぐぐってみたところ、意外とたくさん関連する情報が出てきました。

トップに出てきたブログはredmineプラグイン作者*2でもある@akiko-pusuさんのものでした!

orphanブランチを作ると、空のブランチとして利用できます。

orphanブランチ

redmine_glossaryプラグインのgitクローンを生成します。そして、--orphanオプションを指定してチェックアウトします。ここではブランチ名をreconstructとします。

work$ git clone https://github.com/torutk/redmine_glossary.git
work$ cd redmine_glossary
redmine_glossary$ git checkout --orphan reconstruct
redmine_glossary$ 

この時点で作業ツリーにはmasterのファイル群がそのまま存在します。作業ツリーの状態を確認します。

redmine_glossary$ git status
On branch reconstruct

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   README.ja.rdoc
        new file:   README.rdoc
        :

全てのファイルが新規扱いとなっており、コミットは1件もない状態ということが分かります。

空のブランチで開始したいので、master群のファイルがステージングされているのが邪魔です。また、作業ツリーに残っていても面倒です。

redmine_glossary$ git reset --hard
redmine_glossary$ ls -a
./  ../  .git/
redmine_glossary$

これで空のブランチができました。

ブランチが生成されていない?

redmine_glossary$ git branch -a
master
remotes/origin/HEAD -> origin/msater
remotes/origin/master

ブランチ一覧を見ると、ophanブランチが存在していません。なにか1つコミットしないとダメなようです。
README.rdocをコミットします。

  • README.rdoc
= redmine_glossary

This is a plugin for Redmine to create a glossary that is a list of terms in a project.

To adapt Redmine 4.0(Rails 5.1), reconstruct a glossary plugin from scratch.
redmine_glossary$ git add README.rdoc
redmine_glossary$ git commit -m "orphan branch for reconstructing from scratch"
  :
redmine_glossary$ git branch
  master
* reconstruct

ブランチに登場しました。これをリモートにプッシュします。

redmine_glossary$ git push -u origin reconstruct
  :

リモートのブランチをクローン

glossaryプラグインをゼロから開発します。Redmine 4.0開発版をチェックアウトし、pluginsディレクトリの下で先ほど作成したreconstructブランチをクローンします。

plugins$ git clone -b reconstruct https://github.com/torutk/redmine_glossary.git
  :

README.rdocファイルしかないので、プラグインの雛形を上書き生成します。

plugins$ bundle exec rails generate redmine_plugin redmine_glossary
      create  plugins/redmine_glossary/app
      create  plugins/redmine_glossary/app/controllers
      create  plugins/redmine_glossary/app/helpers
       : (中略)
    conflict  plugins/redmine_glossary/README.rdoc
Overwrite /mnt/d/toru/Documents/study/redminew/plugin_dev/trunk_glossary_dev/plugins/redmine_glossary/README.rdoc? (ent
r "h" for help) [Ynaqdh] n
        skip  plugins/redmine_glossary/README.rdoc
      create  plugins/redmine_glossary/init.rb
      :

README.rdocが存在するため、プラグインの雛形生成時に衝突が検出されます。nで雛形では上書きせずにスキップさせます。

*1:「新しいリポジトリを作ればいいのに」という意見があることは想像できます。ここではリポジトリを増やしたくない、既存リポジトリの再構築なので分けたくないというところです。

*2:定番プラグインであるissue_template、bannar

redmine trunkを落としてglossary pluginが動くか試してみる(続々々)

id:torutk:20180423redmine trunkを落としてglossary pluginが動くか試してみる(続々) - torutkのブログ の続きです。

Glossary PluginのRedmine 4.0対応難航中

もう、「RE:ゼロから始めるGlossary Plugin開発」にタイトル変えて一から作っていきたくなる心境ですが、それも茨の道なので(幾度か頓挫)、もうしばらく現行のGlossary Pluginの対応を進めてみたいと思います。

acts_as_list がない件の調査

Ruby on Rails 1.xの頃にはacts_as_listがあって、それ以降はなくなったといった記述をググっているときに見かけたのですが、URLメモ忘れています。

で、Redmine 3.4でどうなっているか調べると、Redmine本体の次のディレクトリにacts_as_listのコードがありました。

<Redmineインストールディレクトリ>/lib/plugins/acts_as_list/

では、Redmineの最新開発版(trunk)を見てみると、見当たりません。lib/pluginsの下は次の様になっていました。

acts_as_activity_provider  acts_as_customizable  acts_as_searchable  acts_as_watchable  open_id_authentication
acts_as_attachable         acts_as_event         acts_as_tree        gravatar

acts_as_listとatcs_as_versionedの2つが消えています。

対策

一から作るのは大変そうなので、GlossaryプラグインディレクトリにGemfileを作成し、次を記述します。

gem 'acts_as_list'

Redmineルートディレクトリで次を実行します。

$ bundle install
  :
Fetching acts_as_list 0.9.11
Installing acts_as_list 0.9.11
  :

acts_as_listのgemがインストールされます。

サーバーを実行すると、一歩前進することができました。

term_category.rb の attr_accessible

まだ未対処だったので対処が必要です。

term_category.rb から attr_accessibleを削除します。
次に、term_categories_controller.rb にストロングパラメータのコード(メソッド)を追記し、そのメソッドを呼び出す場所を探しました。しかし、term_categories_controller.rb を見ると、TermCategory.where あるいは TermCategory.find_by で検索しているコードはありますが、TermCategoryをnewしているコードが見当たりません。

どうしたものかと、とりあえずサーバーを再実行し様子を見てみます。プロジェクトメニューの[用語集]をクリックすると、めでたく用語集画面が表示されました。続いて、用語定義を作っている途中、カテゴリの作成でエラーとなりました。

ActiveModel::ForbiddenAttributesError in GlossaryController#add_term_category
ActiveModel::ForbiddenAttributesError
Extracted source (around line #121):
  def add_term_category
    @category = TermCategory.new(params[:category])
    @category.project_id = @project.id
  :
plugins/redmine_glossary/app/controllers/glossary_controller.rb:121:in `add_term_category'

先ほど、term_categories_controller.rbにはTermCategory.newが見当たらなかったのですが、glossary_controller.rbに記載されていたのですね。うーむ、ストロングパラメータのメソッドをterm_categories_controller.rbからglossary_controller.rbに移動して、newの際に呼び出すように修正します。

が、上述のコードでTermCategoryをnewするときに渡されるパラメータの中身はproject_idだけになっている模様。ストロングパラメータのメソッドの中で
require(:category)としてもActionController::ParameterMissing (param is missing or the value is empty: category)とエラーになってしまいました。

このあたりでしっかりコントローラーに渡されるパラメータ(params)の中身と、ストロングパラメータの仕組みについて理解をしないと対処が困難になりました。

本日確認した要修正事項

  • acts_as_listの対応は、プラグインのGemfileにacts_as_listを追加する
  • TermCategoryのインスタンス生成(データベースへの書き込み)はTermCategoriesControllerではなく、GlossaryControllerにあるのでストロングパラメータの記述をどこに書くか、また渡されるパラメータが何かを確認する必要あり

redmine trunkを落としてglossary pluginが動くか試してみる(続々)

redmine trunkを落としてglossary pluginが動くか試してみる(続) - torutkのブログ の続きです。

現時点でのRedmineアップデートへのプラグイン追従の感触

ここまで進めてみて、Redmine 3.4(Rails 4.2)からRedmine 4.0(Rails 5.1)へのアップデートでは、Redmine プラグインの追従が相当に遅れる気配が漂ってきました。これは大変だぞ〜うというのが現時点の感触です。

Rails 5.0では、非互換(DEPRECATION)が警告としてログに出るけど動作はするようです。しかし、RedmineのtrunkはRails 5.1なので非互換はすでに削除されているので実行時エラーとなってしまいます。

そこで、この一連の日記に書いているように、実行時エラーが発生、直して動かして、次の実行時エラーにあってまた直して動かして・・・と泥縄的な対応をしています。

せめて、「Redmine 4.0で動くためのプラグイン修正項目と修正方法はこれだ!」
ガイドがあればいいのですが*1…。

気を取り直して進めていきましょう。

まずは昨日出くわしたエラー before_filterから続けます。

before_filterがない

これは、before_actionに変えればいいとありますが、どう変えるのか分かりませんでした。いろいろ調べたところ、どうやら単純にfilterをactionに変えれるだけでいいようです。

ということで、before_filterをbefore_actionに単純修正して次に進みます。
修正したら、サーバーを再起動します。そうしないと、autoload何とかエラーが出ます。

attr_accessibleがundefined method

次のエラー画面が表示されました。

NoMethodError in GlossaryController#index
undefined method `attr_accessible' for #<Class:0x00007f2cc85bcf88>
Extracted source (around line #14):

  belongs_to :project

  attr_accessible :groupby
  
  def grouping?
    case groupby

対処方法はなんでしょうか?調べていくと「ストロングパラメーター」なるキーワードが出てきました。

見よう見まねで、次の対処をしてみました。

  • GlossaryStyleモデル(app/models/glossary_style.rb)のattr_accessible行を削除
  • GlossaryStylesControllerコントローラ(app/controllers/glossary_styles_controller.rb)にprivateなメソッド追加
  private

    def glossary_style_params
      params.require(:glossary_style).permit(:groupby)
    end

同様に、Termモデル、GlossaryControllerコントローラーにも修正を入れます。

undefined method `acts_as_list'
NoMethodError in Glossary#index

Showing path/to/plugins/redmine_glossary/app/views/glossary_styles/_search.html.erb where line #22 raised:

undefined method `acts_as_list' for #<Class:0x00007f0a751c37a8>

Extracted source (around line #5):
  has_many :terms, :foreign_key => 'category_id', :dependent => :nullify
  
  acts_as_list :scope => :project_id
  
  attr_accessible :name, :project_id, :position

今回確認した要修正事項

  • before_filterは、単純に字面をbefore_actionにする(s/filter/action/g)。
  • attr_accessibleはストロングパラメーターに置き換える。あるいはgemのprotected_attributesを追加する。

https://github.com/rails/protected_attributes

  • acts_as_listを何とかする。

*1:お前が書けばよかろう、と言われかねませんが、RubyRailsもロクすっぽ分からない身なので遅々も進まずです。

redmine trunkを落としてglossary pluginが動くか試してみる(続)

redmine trunkを落としてglossary pluginが動くか試してみる - torutkのブログ の続きです。
来るべきRedmine 4.0に備えて、Glossary Pluginが動作するかどうか(動作しないことが確認済み)、修正事項の調査をしています。

調査の詳細は、次の個人Redmineのチケットに記載中です。
http://www.torutk.com/issues/81

前回確認した要修正事項

  • Redmine 4.0(Ruby on Rials 5.1)で削除されたalias_method_chainを書き換える

先に進めてみる

Ruby on Rails 4.xまでの alias_method_chainは、ruby 2.0の標準機能 Module#prepend で書き換えるのだそうですが、書き換え方がよく分からず、当てずっぽうな修正で次に進んでみました。

マイグレーション時のエラー

すると、プラグインマイグレーションで新たなエラーが出ました。

StandardError: Directly inheriting from ActiveRecord::Migration is not supported. Please specify the Rails release the migration was written for:

  class CreateGlossaryStyles < ActiveRecord::Migration[4.2]

プラグインディレクトリのdb/migrate/下にあるコードではクラス宣言に

class CreateGlossaryStyles < ActiveRecord::Migration

と記述されていますが、ActiveRecord::Migrationを直接使うのはダメで、Railsのリリースバージョンを指定するのが必要なようです。

class CreateGlossaryStyles < ActiveRecord::Migration[5.1]
用語集メニュークリックでエラー

マイグレーションが実行できたので、Redmineを実行し、Glossary Pluginを使うプロジェクトを作成し、メニューの[用語集]をクリックしたところ次のエラーとなりました。*1

undefined method `before_filter' for GlossaryController:Class

Rails.root: /path/to/trunk
Application Trace | Framework Trace | Full Trace

plugins/redmine_glossary/app/controllers/glossary_controller.rb:7:in `<class:GlossaryController>'
plugins/redmine_glossary/app/controllers/glossary_controller.rb:2:in `<top (required)>'

before_filterは、プラグインを作成するときにほぼ必須の手段なので、なかなかに厳しい非互換なところです。

用語集の設定クリックでエラー

Redmineの[管理]>[プラグイン]で、Glossary Pluginの[設定]をクリックすると次のエラーとなりました。

undefined method `attr_accessible' for #<Class:0x00007fffc16dea70>

今回確認した要修正事項

  • db/migrate/の下のコードでは、ActiveRecord::Migrationを継承している宣言をActiveRecord::Migration[5.1]に修正する必要がある。
  • before_filterを使っているコードを何かに修正する必要がある
  • attr_accessibleを使っているコードを何かに修正する必要がある

その他

デフォルトで含まれるサーバーがWEBrickからPumaに変わっている模様

Redmineのtrunkをインストールしているときに、gemでpumaを取り込んでいるのが見えました。

プラグイン開発用の環境でRedmineの標準サーバーを起動すると、

$ bundle exec rails server -b 0.0.0.0
=> Booting Puma
=> Rails 5.1.6 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

とPumaが実行されました。

*1:パスの表記は長いので一部削除

redmine trunkを落としてglossary pluginが動くか試してみる

はじめに

Redmine は現在3.4系列がリリースされている最新ブランチで3.4.5がリリースされてました。また、次はメジャーバージョンアップの4.0系列がリリースに向けて開発中です。

Redmine 3.4から4.0への更新では、ベースとなっているRuby on Railsのバージョンが4.2から5.1に変わります。これは大きな更新で、Redmineのエコシステムを支えている多数のプラグインへの影響がいろいろと懸念されます。

Redmineの開発はリポジトリのtrunkで行われているので、redmineリポジトリからtrunkを落とし、その上でglossary plugin(用語集プラグイン)が動くかどうかを試してみることにしました。

動作環境

自宅ではWindows 10マシンを使っていますが、Redmineを動かすなら断然Linuxです。ですが、Linuxマシンを用意できないこともあります。自宅ではそこで、Windows Subsystem for Linux 機能を有効にし、マイクロソフトストアからLinuxディストリビューションをインストールします。
インストールについては次のWikiに記載しています。

Windows Subsystem for Linuxの概要 - ソフトウェアエンジニアリング - Torutk

Redmine 4.0の開発リポジトリから落としてきます。RedmineリポジトリSubversionsvn)で管理されていますが、Gitのミラーリポジトリもあるのでそっちから落としました。svnのtrunkは、Gitではmasterブランチと思われます。

~> git clone https://github.com/redmine/redmine.git

プラグイン開発用にredmineを動かす設定は次のWikiに記載しています。

Redmineプラグイン開発環境 - ソフトウェアエンジニアリング - Torutk

rubyのバージョンが・・・

openSUSE Leap 42.3は、ruby 2.1が標準です。しかし、Ruby on Rails 5.1は、ruby 2.2以上を要求するのでこのままでは動きません。運用環境ではrbenvは好まないのですが、Windows Subsystem for Linuxはほぼ個人環境なのでrbenvでもいいかと思いこれでrubyの新しいバージョンを利用します。

まずrubyのビルドに必要な追加パッケージを入れておきます。

~> sudo zypper install readline-devel openssl-devel

rbenvの環境を用意します。

~> git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
  :(略)
~> git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

環境設定にrbenvを追加します。

~> echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
~> echo 'eval "$(rbenv init -)"' >> ~/.bashrc

新たにbashを起動し、rbenvでrubyをインストールします。

~> rbenv install --list
Available versions:
  1.8.5-p52
  1.8.5-p113
    :(中略)
  2.5.0
  2.5.1
  2.6.0-dev
  2.6.0-preview1
  jruby-1.5.6
    :(後略)

と出るので、最新リリース版の2.5.1を入れます。

~> rbenv install 2.5.1
Downloading ruby-2.5.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.1.tar.bz2
Installing ruby-2.5.1...
  :(ここで時間がかかる)
Installed ruby-2.5.1 to /home/toru/.rbenv/versions/2.5.1
~>

インストールされているrubyバージョンの確認をします。

~> rbenv versions
* system (set by /home/toru/.rbenv/version)
  2.5.1

システム(openSUSE標準パッケージのruby 2.1)が選択されています。そこで、今入れた2.5.1を使うよう設定します。

~> rbenv global 2.5.1
~> rbenv versions
  system
* 2.5.1 (set by /home/toru/.rbenv/version)

rubyのバージョンを切替えた後でbundlerを入れますが、gemの中にnativeビルドするものがあるのでOS標準パッケージを追加しておきます。

~> sudo zypper install autoconf

次のOS標準パッケージも必要でしたので入れておきます。

~> sudo zypper install timezone

Glossaryプラグインを入れる

plugin> git clone https://github.com/torutk/redmine_glossary.git

プラグインマイグレーション実行でエラーになりました。

> bundle exec rake redmine:plugins:migrate
rake aborted!
NoMethodError: undefined method `alias_method_chain' for ActionView::Helpers::AssetTagHelper:Module
Did you mean?  alias_method
  :
>

Ruby on Rails 5で抹消されるAPIの一つ alias_method_chain でエラーがやはり発生しました。

ということで、Redmine 4.0リリースに向けてプラグインの対応が必要なことが再確認できました。

VC++ 64bit版の関数呼び出し時はレジスタが多用されている

はじめに

Windows OS上で、とあるアプリケーションソフトウェアがしばしばクラッシュ(異常終了)します。利用者の立場なので、デバッガーで…という環境はありません。開発元で対処されるのを待つのみなのですが、運用環境でしか再現しないときなどは対処が難しくなります。

運用環境でできる対処として、UNIX系OSでは、アプリケーションソフトウェアがクラッシュするときの問題解決アプローチとして、コアダンプを生成する設定をして、生成されたコアダンプをデバッガ(gdb等)で解析するという方法があります。

そこで、Windows OSで同様なアプローチがあるかを調べると、クラッシュダンプを生成し、そのダンプファイルをデバッガ(WinDbg)で解析するという方法がありました。簡単なメモを次のWikiに書いています。

Windowsでアプリケーション異常終了の原因を追及する - ソフトウェアエンジニアリング - Torutk

クラッシュダンプをデバッガで解析すると、クラッシュした箇所のアセンブリニーモニック)が得られます。そのあたりを調べていると、64bit版のプログラム(AMD64)では関数の引数がレジスタを介して渡されるということを知りました。

32bit版までの理解では、引数をスタックに積んで受け渡し、スタックに積む順番にいくつか種類があり、代表的なものにstdcall, cdeclがある(その他にはfastcall, thiscallなど)といったところです。

そこで、64bitコードでの関数呼び出しを少しかじってみることにします。

Windows 64bitコードでの関数呼び出し

Microsoftが定めるWindows OS上の64bitコードが使用する関数の呼び出し方法(ABI:Application Binary Interface)では、関数の引数のうち最初の4個がレジスタに割当てられます。5個目以降はスタックに割当てられます。戻り値はRAXレジスタです。

Parameter Passing

引数の位置 整数*1 浮動小数点数
第1引数 RCX XMM0
第2引数 RDX XMM1
第3引数 R8 XMM2
第4引数 R9 XMM3

実際にどのようなコードが生成されるかを、Visual Studio 2015 Community EditionのVC++で試してみました。

サンプルコードの記述およびコンパイルにおけるメモ

  • C++コードではシンボル(関数名)がマングリングされるので、extern "C"で関数を宣言します。
  • Releaseビルドをします。
  • ごく単純なサンプルコードは最適化でインライン展開され、また無効コードとして削除されるといった影響があるので、コンパイルオプションに/Ob1(コンパイラ判断ではインライン展開しない)を追加します。
  • 引数を無理やりにでも使うコードを記述します。
  • プロジェクトのプロパティで[C/C++] > [出力ファイル]でアセンブリの出力を指定します(アセンブリコード、コンピューター語コード、ソースコード(/FAcs))。
引数が1個(整数)の関数
int oneArg(int a)
{
	int value = a + 1;
	return value;
}

ビルドすると、コンパイルされたオブジェクトファイル(*.obj)と同じディレクトリに、アセンブリコードのファイル(*.cod)が生成されます。

第1引数は整数なのでRCXレジスタが、戻り値はRAXレジスタが使われるコードが生成されると期待して生成されたアセンブリコードを見てみましょう。

;	COMDAT oneArg
_TEXT	SEGMENT
a$ = 8
oneArg	PROC						; COMDAT

; 5    : 	int value = a + 1;

  00000	8d 41 01	 lea	 eax, DWORD PTR [rcx+1]

; 6    : 	return value;
; 7    : }

  00003	c3		 ret	 0
oneArg	ENDP
_TEXT	ENDS

EAXレジスタは、戻り値を入れるRAXレジスタの下位32bitなので、戻り値型であるint型整数(32bit)が格納される場所で間違いなさそうです。

RCXレジスタは、第1引数を入れるレジスタなのでこれも間違いなさそうです。
ソースコードでは、第1引数の値に1を加算していますが、[rcs+1]で表現されているように見えます。

さて、ここでLEA命令が生成されています。LEA命令を調べると、アドレス計算の命令ですが、転じて整数の計算に使われています。ここでのコードは、RCXレジスタの値に1を加算した値をEAXレジスタに入れるものです。

引数が1個の関数を呼び出すコード
	int arg = std::stoi(str);
	int ret1 = oneArg(arg);

生成されたアセンブリコードは次です。

; 13   : 	int ret1 = oneArg(arg);

  000b7	8b cf		 mov	 ecx, edi
  000b9	e8 00 00 00 00	 call	 oneArg
  000be	44 8b d0	 mov	 r10d, eax

なお、EDIレジスタには前段の処理で、oneArg関数の引数として渡す値が入っています。
第1引数RCXレジスタの下位32bitであるECXレジスタに値を入れたあと関数呼び出しを行います。
戻り値であるEAXレジスタの値をR10レジスタの下位32bitに入れています。

push, popがなくすっきりしたコードになっています。

引数が4個(整数)の関数
int fourArgs(int a, int b, int c, int d)
{
	int alfa = a + b;
	int bravo = c * d;
	int ret = alfa + bravo;
	return ret;
}||<

第1引数aがRCX、第2引数bがRDX、第3引数cがR8、第4引数dがR9の各レジスタに入れて渡され、戻り値はRAXレジスタが使われるコードが生成されると期待されます。生成されたアセンブリコードを見てみましょう。

>|asm|
fourArgs PROC						; COMDAT

; 5    : 	int alfa = a + b;
; 6    : 	int bravo = c * d;

  00000	45 0f af c1	 imul	 r8d, r9d

; 7    : 	int ret = alfa + bravo;

  00004	42 8d 04 01	 lea	 eax, DWORD PTR [rcx+r8]
  00008	03 c2		 add	 eax, edx

; 8    : 	return ret;
; 9    : }

  0000a	c3		 ret	 0
fourArgs ENDP

最初に第3引数cと第4引数dの掛け算のコードが生成されています。レジスタR8DはR8レジスタの下位32bitで、R9DはR9レジスタの下位32bitです。

imul命令でオペランドレジスタ2つの掛け算が実行され、結果はR8レジスタに上書きされます。

lea命令で、第1引数aと、imul命令結果(c * d)が加算され、EAXレジスタに格納されます。

続くadd命令で、EAXレジスタ( a + c * d)と第2引数bが加算され、EAXレジスタに格納され、関数からリターンします。

引数が4個の関数を呼び出すコード
	int ret2 = fourArgs(ret1, ret1 * 2, ret1 * 3, ret1 * 4);

生成されたアセンブリコードは次です。

; 14   : 	int ret2 = fourArgs(ret1, ret1 * 2, ret1 * 3, ret1 * 4);

  000c1	44 8d 0c 85 00
	00 00 00	 lea	 r9d, DWORD PTR [rax*4]
  000c9	44 8d 04 40	 lea	 r8d, DWORD PTR [rax+rax*2]
  000cd	8d 14 00	 lea	 edx, DWORD PTR [rax+rax]
  000d0	8b c8		 mov	 ecx, eax
  000d2	e8 00 00 00 00	 call	 fourArgs
  000d7	8b d8		 mov	 ebx, eax

fourArgs関数の引数1〜4に渡すの値を、lea命令で計算しています。計算結果をそれぞれ引数2(RDXの下位32bitであるEDXレジスタ)、引数3(R8の下位32bitであるR8Dレジスタ)、引数4(R9の下位32bitであるR9Dレジスタ)、引数1(RCXの下位32bitであるECXレジスタ)に入れて、callで関数を呼び出しています。

ここまで関数の引数には、スタックは使われずレジスタで受け渡しされています。

引数が5個(整数、浮動小数点数混在)の関数
int fiveArgs(int a, double b, char* c, int d, int e)
{
	int v1 = a + d + e;
	double v2 = v1 / b;
	double v3 = v2 + *c;
	int ret = oneArg((int)v3);
	return ret;
}

第1引数aがRCX、第2引数bがXMM1、第3引数cがR8、第4引数dがR9の各レジスタに入れて渡され、第5引数eはスタックに積まれて渡され、戻り値はRAXレジスタが使われるコードが生成されると期待されます。生成されたアセンブリコードを見てみましょう。

fiveArgs PROC						; COMDAT

; 5    : 	int v1 = a + d + e;

  00000	42 8d 04 09	 lea	 eax, DWORD PTR [rcx+r9]
  00004	03 44 24 28	 add	 eax, DWORD PTR e$[rsp]
  00008	66 0f 6e d0	 movd	 xmm2, eax

; 6    : 	double v2 = v1 / b;
; 7    : 	double v3 = v2 + *c;
; 8    : 	int ret = oneArg((int)v3);
; 9    : 	return ret;

  0000c	41 0f be 00	 movsx	 eax, BYTE PTR [r8]
  00010	f3 0f e6 d2	 cvtdq2pd xmm2, xmm2
  00014	66 0f 6e c0	 movd	 xmm0, eax
  00018	f2 0f 5e d1	 divsd	 xmm2, xmm1
  0001c	f3 0f e6 c0	 cvtdq2pd xmm0, xmm0
  00020	f2 0f 58 d0	 addsd	 xmm2, xmm0
  00024	f2 0f 2c ca	 cvttsd2si ecx, xmm2
  00028	e9 00 00 00 00	 jmp	 oneArg
fiveArgs ENDP

ちょっと複雑になってきました。
最初の計算では、第1引数aがRCX、第4引数dがR9、第5引数はスタックにあるのですが、pop命令ではなく、スタックポインタRSPにオフセットe$を加えた場所から値を取ってきて加算しています。

5個目の引数がスタックから取られていることが分かりました。
以降は省略します。

引数が4個の関数を呼び出すコード
 15   : 	int ret3 = fiveArgs(argc, arg, (char*)argv, arg, arg);

  000dd	f3 0f e6 c9	 cvtdq2pd xmm1, xmm1
  000e1	89 74 24 20	 mov	 DWORD PTR [rsp+32], esi
  000e5	44 8b ce	 mov	 r9d, esi
  000e8	4c 8b c7	 mov	 r8, rdi
  000eb	8b cd		 mov	 ecx, ebp
  000ed	e8 00 00 00 00	 call	 fiveArgs

第1引数にRCX(ECX)、第2引数にXMM1、第3引数にR8、第4引数にR9(R9D)、第5引数はスタックに値を入れています。

スタックには、pushではなく、スタックポインタ(RSP)からのオフセットにmov命令で値を入れています。

ということで

アプリケーションの利用者という立場で、アプリケーションがクラッシュしたときにできることはクラッシュダンプを生成し、ダンプファイルを読み込んでヒントを得るといった程度しかできません。

また、アプリケーションプログラムのシンボル情報が得られない場合、クラッシュダンプを解析してもアセンブリコードレベルでしか調査ができません。

そこで、Windowsの64bitプログラムのアセンブリコードレベルでの仕組みを少しだけ触ってみました。

インテルの64bit IA-32命令のリファレンスガイドは次から入手できます(現時点では、日本語版は提供されていません)

Intel® 64 and IA-32 Architectures Software Developer Manuals | Intel® Software

なお、UNIX系の64bitプログラムでは、6個までの引数がレジスタ渡しとなるようです。

その他メモ

Visual Studio 2015 Community Editionのライセンスの更新

Visual Studio 2015 Community Editionを久々に起動しました。すると、「ライセンスの更新が必要」と出て使用することができませんでした。
マイクロソフトアカウントを入力して更新すると、使えるようになりましたので、どうやらインストールしてからは一定期間毎にマイクロソフトアカウントを入力しないとダメなようです。インターネット接続環境が必須のようですね。

Windows OS標準ライブラリのシンボル情報

インターネットに接続がマスト、インターネット接続ができない環境では、いったんインターネット接続できるマシンでシンボル情報を取得し、取得したファイル群を持っていく方法となります。

ただし、DLLのバージョンが一致しているマシンでないとずれるので、同じマシンを用意するのが大変です。

  • シンボル情報を取得するマシンをインターネットに接続したとたん、Windowsアップデートがかかってバージョンがずれてしまうとか…

*1:ポインター、64bit以下の構造体を含む