# 調試 Rails 程序
本文介紹如何調試 Rails 程序。
讀完本文,你將學到:
* 調試的目的;
* 如何追查測試沒有發現的問題;
* 不同的調試方法;
* 如何分析調用堆棧;
### Chapters
1. [調試相關的視圖幫助方法](#%E8%B0%83%E8%AF%95%E7%9B%B8%E5%85%B3%E7%9A%84%E8%A7%86%E5%9B%BE%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95)
* [`debug`](#debug)
* [`to_yaml`](#to_yaml)
* [`inspect`](#inspect)
2. [Logger](#logger)
* [Logger 是什么](#logger-%E6%98%AF%E4%BB%80%E4%B9%88)
* [日志等級](#%E6%97%A5%E5%BF%97%E7%AD%89%E7%BA%A7)
* [寫日志](#%E5%86%99%E6%97%A5%E5%BF%97)
* [日志標簽](#%E6%97%A5%E5%BF%97%E6%A0%87%E7%AD%BE)
* [日志對性能的影響](#%E6%97%A5%E5%BF%97%E5%AF%B9%E6%80%A7%E8%83%BD%E7%9A%84%E5%BD%B1%E5%93%8D)
3. [使用 `debugger` gem 調試](#%E4%BD%BF%E7%94%A8-debugger-gem-%E8%B0%83%E8%AF%95)
* [安裝](#%E5%AE%89%E8%A3%85)
* [Shell](#shell)
* [上下文](#%E4%B8%8A%E4%B8%8B%E6%96%87)
* [線程](#%E7%BA%BF%E7%A8%8B)
* [審查變量](#%E5%AE%A1%E6%9F%A5%E5%8F%98%E9%87%8F)
* [逐步執行](#%E9%80%90%E6%AD%A5%E6%89%A7%E8%A1%8C)
* [斷點](#%E6%96%AD%E7%82%B9)
* [捕獲異常](#%E6%8D%95%E8%8E%B7%E5%BC%82%E5%B8%B8)
* [恢復執行](#%E6%81%A2%E5%A4%8D%E6%89%A7%E8%A1%8C)
* [編輯](#%E7%BC%96%E8%BE%91)
* [退出](#%E9%80%80%E5%87%BA)
* [設置](#%E8%AE%BE%E7%BD%AE)
4. [調試內存泄露](#%E8%B0%83%E8%AF%95%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2)
* [Valgrind](#valgrind)
5. [用于調試的插件](#%E7%94%A8%E4%BA%8E%E8%B0%83%E8%AF%95%E7%9A%84%E6%8F%92%E4%BB%B6)
6. [參考資源](#%E5%8F%82%E8%80%83%E8%B5%84%E6%BA%90)
### 1 調試相關的視圖幫助方法
調試一個常見的需求是查看變量的值。在 Rails 中,可以使用下面這三個方法:
* `debug`
* `to_yaml`
* `inspect`
#### 1.1 `debug`
`debug` 方法使用 YAML 格式渲染對象,把結果包含在 `<pre>` 標簽中,可以把任何對象轉換成人類可讀的數據格式。例如,在視圖中有以下代碼:
```
<%= debug @post %>
<p>
<b>Title:</b>
<%= @post.title %>
</p>
```
渲染后會看到如下結果:
```
--- !ruby/object:Post
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
Title: Rails debugging guide
```
#### 1.2 `to_yaml`
使用 YAML 格式顯示實例變量、對象的值或者方法的返回值,可以這么做:
```
<%= simple_format @post.to_yaml %>
<p>
<b>Title:</b>
<%= @post.title %>
</p>
```
`to_yaml` 方法把對象轉換成可讀性較好地 YAML 格式,`simple_format` 方法按照終端中的方式渲染每一行。`debug` 方法就是包裝了這兩個步驟。
上述代碼在渲染后的頁面中會顯示如下內容:
```
--- !ruby/object:Post
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
Title: Rails debugging guide
```
#### 1.3 `inspect`
另一個用于顯示對象值的方法是 `inspect`,顯示數組和 Hash 時使用這個方法特別方便。`inspect` 方法以字符串的形式顯示對象的值。例如:
```
<%= [1, 2, 3, 4, 5].inspect %>
<p>
<b>Title:</b>
<%= @post.title %>
</p>
```
渲染后得到的結果如下:
```
[1, 2, 3, 4, 5]
Title: Rails debugging guide
```
### 2 Logger
運行時把信息寫入日志文件也很有用。Rails 分別為各運行環境都維護著單獨的日志文件。
#### 2.1 Logger 是什么
Rails 使用 `ActiveSupport::Logger` 類把信息寫入日志。當然也可換用其他代碼庫,比如 `Log4r`。
替換日志代碼庫可以在 `environment.rb` 或其他環境文件中設置:
```
Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")
```
默認情況下,日志文件都保存在 `Rails.root/log/` 文件夾中,日志文件名為 `environment_name.log`。
#### 2.2 日志等級
如果消息的日志等級等于或高于設定的等級,就會寫入對應的日志文件中。如果想知道當前的日志等級,可以調用 `Rails.logger.level` 方法。
可用的日志等級包括:`:debug`,`:info`,`:warn`,`:error`,`:fatal` 和 `:unknown`,分別對應數字 0-5。修改默認日志等級的方式如下:
```
config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time
```
這么設置在開發環境和交付準備環境中很有用,在生產環境中則不會寫入大量不必要的信息。
Rails 所有環境的默認日志等級是 `debug`。
#### 2.3 寫日志
把消息寫入日志文件可以在控制器、模型或郵件發送程序中調用 `logger.(debug|info|warn|error|fatal)` 方法。
```
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
logger.info "Processing the request..."
logger.fatal "Terminating application, raised unrecoverable error!!!"
```
下面這個例子增加了額外的寫日志功能:
```
class PostsController < ApplicationController
# ...
def create
@post = Post.new(params[:post])
logger.debug "New post: #{@post.attributes.inspect}"
logger.debug "Post should be valid: #{@post.valid?}"
if @post.save
flash[:notice] = 'Post was successfully created.'
logger.debug "The post was saved and now the user is going to be redirected..."
redirect_to(@post)
else
render action: "new"
end
end
# ...
end
```
執行上述動作后得到的日志如下:
```
Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl
vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4
Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails",
"body"=>"I'm learning how to print in logs!!!", "published"=>"0"},
"authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"posts"}
New post: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!",
"published"=>false, "created_at"=>nil}
Post should be valid: true
Post Create (0.000443) INSERT INTO "posts" ("updated_at", "title", "body", "published",
"created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails',
'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54')
The post was saved and now the user is going to be redirected...
Redirected to #<Post:0x20af760>
Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]
```
加入這種日志信息有助于發現異常現象。如果添加了額外的日志消息,記得要合理設定日志等級,免得把大量無用的消息寫入生產環境的日志文件。
#### 2.4 日志標簽
運行多用戶/多賬戶的程序時,使用自定義的規則篩選日志信息能節省很多時間。Active Support 中的 `TaggedLogging` 模塊可以實現這種功能,可以在日志消息中加入二級域名、請求 ID 等有助于調試的信息。
```
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff"
logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
```
#### 2.5 日志對性能的影響
如果把日志寫入硬盤,肯定會對程序有點小的性能影響。不過可以做些小調整:`:debug` 等級比 `:fatal` 等級對性能的影響更大,因為寫入的日志消息量更多。
如果按照下面的方式大量調用 `Logger`,也有潛在的問題:
```
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
```
在上述代碼中,即使日志等級不包含 `:debug` 也會對性能產生影響。因為 Ruby 要初始化字符串,再花時間做插值。因此推薦把代碼塊傳給 `logger` 方法,只有等于或大于設定的日志等級時才會執行其中的代碼。重寫后的代碼如下:
```
logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}
```
代碼塊中的內容,即字符串插值,僅當允許 `:debug` 日志等級時才會執行。這種降低性能的方式只有在日志量比較大時才能體現出來,但卻是個好的編程習慣。
### 3 使用 `debugger` gem 調試
如果代碼表現異常,可以在日志文件或者控制臺查找原因。但有時使用這種方法效率不高,無法找到導致問題的根源。如果需要檢查源碼,`debugger` gem 可以助你一臂之力。
如果想學習 Rails 源碼但卻無從下手,也可使用 `debugger` gem。隨便找個請求,然后按照這里介紹的方法,從你編寫的代碼一直研究到 Rails 框架的代碼。
#### 3.1 安裝
`debugger` gem 可以設置斷點,實時查看執行的 Rails 代碼。安裝方法如下:
```
$ gem install debugger
```
從 2.0 版本開始,Rails 內置了調試功能。在任何 Rails 程序中都可以使用 `debugger` 方法調出調試器。
下面舉個例子:
```
class PeopleController < ApplicationController
def new
debugger
@person = Person.new
end
end
```
然后就能在控制臺或者日志中看到如下信息:
```
***** Debugger requested, but was not available: Start server with --debugger to enable *****
```
記得啟動服務器時要加上 `--debugger` 選項:
```
$ rails server --debugger
=> Booting WEBrick
=> Rails 4.2.0 application starting on http://0.0.0.0:3000
=> Debugger enabled
...
```
在開發環境中,如果啟動服務器時沒有指定 `--debugger` 選項,不用重啟服務器,加入 `require "debugger"` 即可。
#### 3.2 Shell
在程序中調用 `debugger` 方法后,會在啟動程序所在的終端窗口中啟用調試器 shell,并進入調試器的終端 `(rdb:n)` 中。其中 `n` 是線程編號。在調試器的終端中會顯示接下來要執行哪行代碼。
如果在瀏覽器中執行的請求觸發了調試器,當前瀏覽器選項卡會處于停頓狀態,等待調試器啟動,跟蹤完整個請求。
例如:
```
@posts = Post.all
(rdb:7)
```
現在可以深入分析程序的代碼了。首先我們來查看一下調試器的幫助信息,輸入 `help`:
```
(rdb:7) help
ruby-debug help v0.10.2
Type 'help <command-name>' for help on a specific command
Available commands:
backtrace delete enable help next quit show trace
break disable eval info p reload source undisplay
catch display exit irb pp restart step up
condition down finish list ps save thread var
continue edit frame method putl set tmate where
```
要想查看某個命令的幫助信息,可以在終端里輸入 `help <command-name>`,例如 `help var`。
接下來要學習最有用的命令之一:`list`。調試器中的命令可以使用簡寫形式,只要輸入的字母數量足夠和其他命令區分即可。因此,可使用 `l` 代替 `list`。
`list` 命令輸出當前執行代碼的前后 5 行代碼。下面的例子中,當前行是第 6 行,前面用 `=>` 符號標記。
```
(rdb:7) list
[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
4 def index
5 debugger
=> 6 @posts = Post.all
7
8 respond_to do |format|
9 format.html # index.html.erb
10 format.json { render json: @posts }
```
如果再次執行 `list` 命令,請用 `l` 試試。接下來要執行的 10 行代碼會顯示出來:
```
(rdb:7) l
[11, 20] in /PathTo/project/app/controllers/posts_controller.rb
11 end
12 end
13
14 # GET /posts/1
15 # GET /posts/1.json
16 def show
17 @post = Post.find(params[:id])
18
19 respond_to do |format|
20 format.html # show.html.erb
```
可以一直這么執行下去,直到文件的末尾。如果到文件末尾了,`list` 命令會回到該文件的開頭,再次從頭開始執行一遍,把文件視為一個環形緩沖。
如果想查看前面 10 行代碼,可以輸入 `list-`(或者 `l-`):
```
(rdb:7) l-
[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
4 def index
5 debugger
6 @posts = Post.all
7
8 respond_to do |format|
9 format.html # index.html.erb
10 format.json { render json: @posts }
```
使用 `list` 命令可以在文件中來回移動,查看 `debugger` 方法所在位置前后的代碼。如果想知道 `debugger` 方法在文件的什么位置,可以輸入 `list=`:
```
(rdb:7) list=
[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
4 def index
5 debugger
=> 6 @posts = Post.all
7
8 respond_to do |format|
9 format.html # index.html.erb
10 format.json { render json: @posts }
```
#### 3.3 上下文
開始調試程序時,會進入堆棧中不同部分對應的不同上下文。
到達一個停止點或者觸發某個事件時,調試器就會創建一個上下文。上下文中包含被終止程序的信息,調試器用這些信息審查調用幀,計算變量的值,以及調試器在程序的什么地方終止執行。
任何時候都可執行 `backtrace` 命令(簡寫形式為 `where`)顯示程序的調用堆棧。這有助于理解如何執行到當前位置。只要你想知道程序是怎么執行到當前代碼的,就可以通過 `backtrace` 命令獲得答案。
```
(rdb:5) where
#0 PostsController.index
at line /PathTo/project/app/controllers/posts_controller.rb:6
#1 Kernel.send
at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
#2 ActionController::Base.perform_action_without_filters
at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
#3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...)
at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617
...
```
執行 `frame n` 命令可以進入指定的調用幀,其中 `n` 為幀序號。
```
(rdb:5) frame 2
#2 ActionController::Base.perform_action_without_filters
at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
```
可用的變量和逐行執行代碼時一樣。畢竟,這就是調試的目的。
向前或向后移動調用幀可以執行 `up [n]`(簡寫形式為 `u`)和 `down [n]` 命令,分別向前或向后移動 n 幀。n 的默認值為 1。向前移動是指向更高的幀數移動,向下移動是指向更低的幀數移動。
#### 3.4 線程
`thread` 命令(縮略形式為 `th`)可以列出所有線程,停止線程,恢復線程,或者在線程之間切換。其選項如下:
* `thread`:顯示當前線程;
* `thread list`:列出所有線程及其狀態,`+` 符號和數字表示當前線程;
* `thread stop n`:停止線程 `n`;
* `thread resume n`:恢復線程 `n`;
* `thread switch n`:把當前線程切換到線程 `n`;
`thread` 命令有很多作用。調試并發線程時,如果想確認代碼中沒有條件競爭,使用這個命令十分方便。
#### 3.5 審查變量
任何表達式都可在當前上下文中運行。如果想計算表達式的值,直接輸入表達式即可。
下面這個例子說明如何查看在當前上下文中 `instance_variables` 的值:
```
@posts = Post.all
(rdb:11) instance_variables
["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"]
```
你可能已經看出來了,在控制器中可使用的所有實例變量都顯示出來了。這個列表隨著代碼的執行會動態更新。例如,使用 `next` 命令執行下一行代碼:
```
(rdb:11) next
Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET]
Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e
Parameters: {"action"=>"index", "controller"=>"posts"}
/PathToProject/posts_controller.rb:8
respond_to do |format|
```
然后再查看 `instance_variables` 的值:
```
(rdb:11) instance_variables.include? "@posts"
true
```
實例變量中出現了 `@posts`,因為執行了定義這個變量的代碼。
執行 `irb` 命令可進入 **irb** 模式,irb 會話使用當前上下文。警告:這是實驗性功能。
`var` 命令是顯示變量值最便捷的方式:
```
var
(rdb:1) v[ar] const <object> show constants of object
(rdb:1) v[ar] g[lobal] show global variables
(rdb:1) v[ar] i[nstance] <object> show instance variables of object
(rdb:1) v[ar] l[ocal] show local variables
```
上述方法可以很輕易的查看當前上下文中的變量值。例如:
```
(rdb:9) var local
__dbg_verbose_save => false
```
審查對象的方法可以使用下述方式:
```
(rdb:9) var instance Post.new
@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"...
@attributes_cache = {}
@new_record = true
```
命令 `p`(print,打印)和 `pp`(pretty print,精美格式化打印)可用來執行 Ruby 表達式并把結果顯示在終端里。
`display` 命令可用來監視變量,查看在代碼執行過程中變量值的變化:
```
(rdb:1) display @recent_comments
1: @recent_comments =
```
`display` 命令后跟的變量值會隨著執行堆棧的推移而變化。如果想停止顯示變量值,可以執行 `undisplay n` 命令,其中 `n` 是變量的代號,在上例中是 `1`。
#### 3.6 逐步執行
現在你知道在運行代碼的什么位置,以及如何查看變量的值。下面我們繼續執行程序。
`step` 命令(縮寫形式為 `s`)可以一直執行程序,直到下一個邏輯停止點,再把控制權交給調試器。
`step+ n` 和 `step- n` 可以相應的向前或向后 `n` 步。
`next` 命令的作用和 `step` 命令類似,但執行的方法不會停止。和 `step` 命令一樣,也可使用加號前進 `n` 步。
`next` 命令和 `step` 命令的區別是,`step` 命令會在執行下一行代碼之前停止,一次只執行一步;`next` 命令會執行下一行代碼,但不跳出方法。
例如,下面這段代碼調用了 `debugger` 方法:
```
class Author < ActiveRecord::Base
has_one :editorial
has_many :comments
def find_recent_comments(limit = 10)
debugger
@recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
end
end
```
在控制臺中也可啟用調試器,但要記得在調用 `debugger` 方法之前先 `require "debugger"`。
```
$ rails console
Loading development environment (Rails 4.2.0)
>> require "debugger"
=> []
>> author = Author.first
=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10">
>> author.find_recent_comments
/PathTo/project/app/models/author.rb:11
)
```
停止執行代碼時,看一下輸出:
```
(rdb:1) list
[2, 9] in /PathTo/project/app/models/author.rb
2 has_one :editorial
3 has_many :comments
4
5 def find_recent_comments(limit = 10)
6 debugger
=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
8 end
9 end
```
在方法內的最后一行停止了。但是這行代碼執行了嗎?你可以審查一下實例變量。
```
(rdb:1) var instance
@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
@attributes_cache = {}
```
`@recent_comments` 還未定義,所以這行代碼還沒執行。執行 `next` 命令執行這行代碼:
```
(rdb:1) next
/PathTo/project/app/models/author.rb:12
@recent_comments
(rdb:1) var instance
@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
@attributes_cache = {}
@comments = []
@recent_comments = []
```
現在看以看到,因為執行了這行代碼,所以加載了 `@comments` 關聯,也定義了 `@recent_comments`。
如果想深入方法和 Rails 代碼執行堆棧,可以使用 `step` 命令,一步一步執行。這是發現代碼問題(或者 Rails 框架問題)最好的方式。
#### 3.7 斷點
斷點設置在何處終止執行代碼。調試器會在斷點設定行調用。
斷點可以使用 `break` 命令(縮寫形式為 `b`)動態添加。設置斷點有三種方式:
* `break line`:在當前源碼文件的第 `line` 行設置斷點;
* `break file:line [if expression]`:在文件 `file` 的第 `line` 行設置斷點。如果指定了表達式 `expression`,其返回結果必須為 `true` 才會啟動調試器;
* `break class(.|\#)method [if expression]`:在 `class` 類的 `method` 方法中設置斷點,`.` 和 `\#` 分別表示類和實例方法。表達式 `expression` 的作用和上個命令一樣;
```
(rdb:5) break 10
Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10
```
`info breakpoints n` 或 `info break n` 命令可以列出斷點。如果指定了數字 `n`,只會列出對應的斷點,否則列出所有斷點。
```
(rdb:5) info breakpoints
Num Enb What
1 y at filters.rb:10
```
如果想刪除斷點,可以執行 `delete n` 命令,刪除編號為 `n` 的斷點。如果不指定數字 `n`,則刪除所有在用的斷點。
```
(rdb:5) delete 1
(rdb:5) info breakpoints
No breakpoints.
```
啟用和禁用斷點的方法如下:
* `enable breakpoints`:允許使用指定的斷點列表或者所有斷點終止執行程序。這是創建斷點后的默認狀態。
* `disable breakpoints`:指定的斷點 `breakpoints` 在程序中不起作用。
#### 3.8 捕獲異常
`catch exception-name` 命令(或 `cat exception-name`)可捕獲 `exception-name` 類型的異常,源碼很有可能沒有處理這個異常。
執行 `catch` 命令可以列出所有可用的捕獲點。
#### 3.9 恢復執行
有兩種方法可以恢復被調試器終止執行的程序:
* `continue [line-specification]`(或 `c`):從停止的地方恢復執行程序,設置的斷點失效。可選的參數 `line-specification` 指定一個代碼行數,設定一個一次性斷點,程序執行到這一行時,斷點會被刪除。
* `finish [frame-number]`(或 `fin`):一直執行程序,直到指定的堆棧幀結束為止。如果沒有指定 `frame-number` 參數,程序會一直執行,直到當前堆棧幀結束為止。當前堆棧幀就是最近剛使用過的幀,如果之前沒有移動幀的位置(執行 `up`,`down` 或 `frame` 命令),就是第 0 幀。如果指定了幀數,則運行到指定的幀結束為止。
#### 3.10 編輯
下面兩種方法可以從調試器中使用編輯器打開源碼:
* `edit [file:line]`:使用環境變量 `EDITOR` 指定的編輯器打開文件 `file`。還可指定文件的行數(`line`)。
* `tmate n`(簡寫形式為 `tm`):在 TextMate 中打開當前文件。如果指定了參數 `n`,則使用第 n 幀。
#### 3.11 退出
要想退出調試器,請執行 `quit` 命令(縮寫形式為 `q`),或者別名 `exit`。
退出后會終止所有線程,所以服務器也會被停止,因此需要重啟。
#### 3.12 設置
`debugger` gem 能自動顯示你正在分析的代碼,在編輯器中修改代碼后,還會重新加載源碼。下面是可用的選項:
* `set reload`:修改代碼后重新加載;
* `set autolist`:在每個斷點處執行 `list` 命令;
* `set listsize n`:設置顯示 `n` 行源碼;
* `set forcestep`:強制 `next` 和 `step` 命令移到終點后的下一行;
執行 `help set` 命令可以查看完整說明。執行 `help set subcommand` 可以查看 `subcommand` 的幫助信息。
設置可以保存到家目錄中的 `.rdebugrc` 文件中。啟動調試器時會讀取這個文件中的全局設置。
下面是 `.rdebugrc` 文件示例:
```
set autolist
set forcestep
set listsize 25
```
### 4 調試內存泄露
Ruby 程序(Rails 或其他)可能會導致內存泄露,泄露可能由 Ruby 代碼引起,也可能由 C 代碼引起。
本節介紹如何使用 Valgrind 等工具查找并修正內存泄露問題。
#### 4.1 Valgrind
[Valgrind](http://valgrind.org/) 這個程序只能在 Linux 系統中使用,用于偵察 C 語言層的內存泄露和條件競爭。
Valgrind 提供了很多工具,可用來偵察內存管理和線程問題,也能詳細分析程序。例如,如果 C 擴展調用了 `malloc()` 函數,但沒調用 `free()` 函數,這部分內存就會一直被占用,直到程序結束。
關于如何安裝 Valgrind 及在 Ruby 中使用,請閱讀 Evan Weaver 編寫的 [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) 一文。
### 5 用于調試的插件
有很多 Rails 插件可以幫助你查找問題和調試程序。下面列出一些常用的調試插件:
* [Footnotes](https://github.com/josevalim/rails-footnotes):在程序的每個頁面底部顯示請求信息,并鏈接到 TextMate 中的源碼;
* [Query Trace](https://github.com/ntalbott/query_trace/tree/master):在日志中寫入請求源信息;
* [Query Reviewer](https://github.com/nesquena/query_reviewer):這個 Rails 插件在開發環境中會在每個 `SELECT` 查詢前執行 `EXPLAIN` 查詢,并在每個頁面中添加一個 `div` 元素,顯示分析到的查詢問題;
* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master):提供了一個郵件發送程序和一組默認的郵件模板,Rails 程序出現問題后發送郵件提醒;
* [Better Errors](https://github.com/charliesome/better_errors):使用全新的頁面替換 Rails 默認的錯誤頁面,顯示更多的上下文信息,例如源碼和變量的值;
* [RailsPanel](https://github.com/dejan/rails_panel):一個 Chrome 插件,在瀏覽器的開發者工具中顯示 `development.log` 文件的內容,顯示的內容包括:數據庫查詢時間,渲染時間,總時間,參數列表,渲染的視圖等。
### 6 參考資源
* [ruby-debug 首頁](http://bashdb.sourceforge.net/ruby-debug/home-page.html)
* [debugger 首頁](https://github.com/cldwalker/debugger)
* [文章:使用 ruby-debug 調試 Rails 程序](http://www.sitepoint.com/debug-rails-app-ruby-debug/)
* [Ryan Bates 制作的視頻“Debugging Ruby (revised)”](http://railscasts.com/episodes/54-debugging-ruby-revised)
* [Ryan Bates 制作的視頻“The Stack Trace”](http://railscasts.com/episodes/24-the-stack-trace)
* [Ryan Bates 制作的視頻“The Logger”](http://railscasts.com/episodes/56-the-logger)
* [使用 ruby-debug 調試](http://bashdb.sourceforge.net/ruby-debug.html)
### 反饋
歡迎幫忙改善指南質量。
如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。
翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。
文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。
最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
- Ruby on Rails 指南 (651bba1)
- 入門
- Rails 入門
- 模型
- Active Record 基礎
- Active Record 數據庫遷移
- Active Record 數據驗證
- Active Record 回調
- Active Record 關聯
- Active Record 查詢
- 視圖
- Action View 基礎
- Rails 布局和視圖渲染
- 表單幫助方法
- 控制器
- Action Controller 簡介
- Rails 路由全解
- 深入
- Active Support 核心擴展
- Rails 國際化 API
- Action Mailer 基礎
- Active Job 基礎
- Rails 程序測試指南
- Rails 安全指南
- 調試 Rails 程序
- 設置 Rails 程序
- Rails 命令行
- Rails 緩存簡介
- Asset Pipeline
- 在 Rails 中使用 JavaScript
- 引擎入門
- Rails 應用的初始化過程
- Autoloading and Reloading Constants
- 擴展 Rails
- Rails 插件入門
- Rails on Rack
- 個性化Rails生成器與模板
- Rails應用模版
- 貢獻 Ruby on Rails
- Contributing to Ruby on Rails
- API Documentation Guidelines
- Ruby on Rails Guides Guidelines
- Ruby on Rails 維護方針
- 發布記
- A Guide for Upgrading Ruby on Rails
- Ruby on Rails 4.2 發布記
- Ruby on Rails 4.1 發布記
- Ruby on Rails 4.0 Release Notes
- Ruby on Rails 3.2 Release Notes
- Ruby on Rails 3.1 Release Notes
- Ruby on Rails 3.0 Release Notes
- Ruby on Rails 2.3 Release Notes
- Ruby on Rails 2.2 Release Notes