Skip to content

Instantly share code, notes, and snippets.

@chitacan
Last active December 11, 2015 17:29
Show Gist options
  • Save chitacan/8a561fffca9e93b76c44 to your computer and use it in GitHub Desktop.
Save chitacan/8a561fffca9e93b76c44 to your computer and use it in GitHub Desktop.
octoberskyjs debug node with gdb 발표자료.

Debug Node with GDB

최종 업데이트 : 2013-10-21 20:15:46

첫 번째 예제. 디버깅은 어떻게 할 수 있을까?

간단한 배경

  • 분명 노드 커미터 애들도 이런식으로 외부 라이브러리를 만들어 쓸꺼다. 그런데 디버깅을 하는 방법이 콘솔출력 뿐이라고?? 아닐것 같다.
  • 분명 자신들이 짠 라이브러리가 노드에서 어떻게 돌아기는지 확인하는 방법이 있을 것이다.
  • 회사에서 jdb를 통해 안드로이드 apk를 소스없이 신나게 디버깅 했기에, 다른 디버거들에 대한 호기심이 증폭되고 있었음!! (lldb 등등)
  • 무엇보다도, 여기서 막하는게 싫었다.
function () { [native code] }

node`s debugger

  • 노드 런타임에 스택 출력

console.trace() 로 스택 트레이스를 찍었는데, 헐 네이티브 호출은 찍히지도 않는다.

  • 노드 디버거는
var hello = addon.hello();

라인은 step in이 되지않고 그대로 넘어가 버린다.

당연한 듯 하다. hello는 자바스크립트의 영역이 아니고, c++ 함수를 바로 호출하는 코드 이니까.

즉, 첫 번째 예제는 (당연하게도!!) 자바스크립트 레이어에선 디버깅이 불가능 !!

아, 맞다

node.js는 이름엔 js가 붙어있는데,

거의 70% 이상이 C/C++ 로 이루어져 있다.(linguist)

$ linguist node/
38%  C++
32%  C
29%  JavaScript
0%   D 
0%   Python
0%   Shell
0%   Objective-C
0%   Ruby
0%   R 

we need C/C++ de-bugger

즉, 우린 C/C++ 로 구성된 거대한 프로젝트에 addon 된 코드를 디버깅 해야됨.

컴파일된 라이브러리를 디버깅하려고 하니 바로 떠오르는건 gdb !!

gdb??

GDB, the GNU Project debugger, allows you to see what is going on `inside' another program while it executes.

make node

gdb로 C/C++ 디버깅을 하려면 노드를 직접 빌드해 설치해야 한다.

checkout

난 최신 stable 버전이 좋으니까 ㅋ

$ git clone https://github.com/joyent/node.git
$ git checkout -b <new_branch_name> origin/v0.8.18-release

config, make, install

$ ./configure --debug
$ make -j4 -C out BUILDTYPE=Debug
$ sudo make install
  • configure 에서 사용할 수 있는 옵션들은 ./configure --help 를 통해 더 확인할 수 있다.
  • 그냥 빌드하면 릴리즈 모드로 빌드되는데, 통상 릴리즈 모드로 빌드 한다는건 디버깅에 필요한 심볼정보들을 빼고 빌드한다는 거다.
  • 이는 binutils에 포함된 gobjdump 를 통해 확인할 수 있다.
$ brew install binutils
$ gobjdump -e <object_file>
  • 근데, release 모드로 빌드해도 -gdwarf 옵션으로 웬만한 바이너리는 디버깅 정보가 들어가 있다;;

node 프로젝트를 release 로 빌드했을 때와 debug로 빌드했을 때의 차이

항목 release debug
make 에 걸리는 시간 2m36.488s 3m13.670s
out 디렉토리의 크기 280Mb 311Mb

tested on Mid 2011 MacBook Air with 1.8GHz i7, 4GB ram, OSX 10.8.2

debug with gdb

인스톨이 끝났으면, 첫 번째 예제 를 테스트 하던 디렉토리로 가서 다음의 명령어를 입력해 보자

$ gdb -d <your_path_to/node/src/> --args node <target.js> 

gdb가 시직된다!!! 그럼, 우리가 hello.node로 빌드한 hello.cc 파일의 7번째 라인에 break point를 걸어보자

(gdb) b hello.cc:7
(gdb) run
Breakpoint 1, Method (args=@0x7fff5fbfef40) at ../hello.cc:7
(gdb) p scope
(gdb) p scope->isolate_
(gdb) ptype scope

gdb commands

자주 사용할만한 주요 명령어를 정리해 보면

명령어 설명
b symbol symbol에 breakpoint 설정
s step
n next
c continue
finish 함수를 끝까지 실행하고 빠져나간다.
return 함수를 실행하지 않고 빠져나간다.(return 값을 줄 수 있음)
i line 현재 break 된 라인에 대한 정보
i threads 스레드에 대한 정보 출력
i source 현재 break된 소스에 대한 정보 출력
i types 현재 로드된 모든 타입 출력
i variables 현재 로드된 모든 변수들, 주소 출력
i b 설정된 break point 확인
i locals 로컬 변수들의 내용 확인
l break 된 소스의 내용 출력
l - 출력된 소스의 이전 내용 출력
l + 출력된 소스의 이후 내용 출력
p 변수의 값 출력
p $(number) 레지스터 값 출력
p *a@3 a 배열의 내용을 3번째까지 출력
bt 스택 프레임 출력

cgdb !!!

on github

$ brew install cgdb
$ cgdb -- -d ~/git/node_project/node/src/ --args node addon.js 
설명
esc 소스 pane 으로
space 소스 pane 에서 breakpoint 설정
i 소스 pane 에서 gdb 터미널로

node require

gdb 가 켜진 김에 module 동작 확인.

  • 우선 node의 디버거로 javascript 함수들이 어떻게 호출되는지 따라가 보자
  • require 가 호출되면 require 인자의 종류 (node, json, js) 에 따라 다른 일이 일어나는데,
  • dynamic lib 인 .node 파일들은 process.dlopen에 의해 로드됨.
  • 근데 process.js 는 어디에??
  • process 는 100% 네이티브로 구현되어 있음 node.cc

V8 의 jit compile

V8은 현재 스크립트의 상황에 따라 어셈코드를 동적으로 막 생성한다. 이로 인해 평상시 노드를 gdb로 디버깅하면서 bt 를 찍으면,

3  0x000011d0e750618e in ?? ()
4  0x000011d0e7531eac in ?? ()
5  0x000011d0e75316ef in ?? ()
6  0x000011d0e752b528 in ?? ()
7  0x000011d0e7532907 in ?? ()
8  0x000011d0e75248c7 in ?? ()
9  0x000011d0e7511317 in ?? ()
10 0x00000001001713cd in Invoke (is_construct=<value temporarily unavailable, due to optimizations>, argc=1, args=0x7fff5fbff4f8, has_pending_exception=0x7fff5fbff4b7) at ../dep
s/v8/src/execution.cc:118

이렇게 중간에 ?? 프레임이 찍힌다.

중간에 조금이라도 정보를 더 보고자 한다면, 여기 참고 (아직 노드 빌드시 저 옵션을 켜는 방법은 모르겠다.)

hack down point

gdb 연결을 위해 노스 소스를 보면서 발견한 분석해볼만한 포인트

  • node 의 make 시스템
  • v8 과 다른 라이브러리간의 인터페이스(특히 libuv >> tick >> api call)
  • 잃어버린 심볼들??

cowsays

 _________________________________ 
< let your hacking begin with GDB >
 --------------------------------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

how do I find an infinite loop without knowing where the loop is?

remy sharp의 질문

If I were using Chrome, I'd simply record the timeline, and capture the spike 
in code, see the line numbers and start to refactor (or some similar approach).

The problem I have is jsbin has a few 1000 line codebase with no obvious 
infinite loop problem.  I can't replicate it myself, but I know that some user 
is able to replicate the issue that triggers the process to go in to 99% cpu 
usage, and all things stop.

Obviously things like nodetime won't work, because the process is in a tight 
loop and won't respond to nodetime's requests.

I've also tried using `strace` on the box, but it actually looks idle, rather 
than looping - which really doesn't make sense to me, but it's quite possible 
that I'm not reading the strace output correctly.

So, clever people, any ideas how to capture and find the source of these kinds 
of issues?

Clever or crazy ideas welcome!

여기에 이전에 v8에서 일했던 Vyacheslav Egorov 가 해결책을 제시했다.

+Remy Sharp

if it is a node application then you can just take a full memory core dump 
once it goes into infinite loop and then you extract stack trace from there.

iI you don't want to unroll JavaScript stack manually you can write a bit of 
Python, unfortunately V8 does not come with good GDB helpers out of the box. 
https://code.google.com/p/v8/source/browse/trunk/tools/gdb-v8-support.py <- 
that's everything it has, support for postmortem debugging on Windows is much 
more substantial, and Joyent has contributed postmortem debugging tools for mdb
.

I used to have some stack unrolling helper for GDB, but I am not sure I will 
be able to find it.

Now with respect to tight loops: V8 actually allows built-in debugger to 
interrupt them (generated code for JavaScript loops must always contain an 
interrrupt check which allows debugger to stop execution, if it does not --- 
that's a bug in V8.). So in theory you should be able to break any loop and 
see where you are.

+Remy Sharp

I unfortunately did not find my stack walking helper but I remembered an 
easier (but nastier) trick to get stack trace out of V8

Tried it with node v0.10.8 that I have on my box and it seems to work:

https://gist.github.com/mraleph/6453431

[but it might be a bit toolchain dependent, as it requires certain symbols to
 be visible to GDB]

+Vyacheslav

Egorov oh, so you're breaking with gdb, then throwing an arbitrary error so 
that you can capture a full stack trace.  Is that right?

+Remy Sharp

kind of.

I break in a random place in gdb (somewhere in jit compiled native code), then 
I put a break point into V8 internals in a place a) which is inside runtime, 
so JS stack is traversable b) which I should be able to hit even if JS code is 
stuck in the infinite loop. This place is inside code that handle V8 stack 
guard interrupts.

Then I ask V8 to schedule a termination interrupt via stack guard and resume 
execution of the process.

Once execution is resumed it is bound to hit a stack guard and fall into 
runtime where I put breakpoint.

At that point I am in a place where V8 itself can traverse the stack. 
Unfortunately because debugging symbols are absent I can't call a function 
that would just print a stack trace, but I can call another function (V8_Fatal)
 that is used to report & crash process on fatal errors in V8. Among things it 
 reports is current JS stack trace. (I could not use this function immediately 
 when I broke inside JS code because V8 would not be able to unwind the stack 
 correctly from the random place, hence all the dance with stack guard).

방법인 즉슨 gdb로 무한루프에 빠진 노드 프로세스에 붙은 뒤 아래의 조건을 충족하는 곳에 브레이크 포인트를 설정하고 V8_Fatal 로 에러를 던져 현재 스택트레이스를 찍는 것 ㄷ

  • which is inside runtime, so JS stack is traversable
  • which I should be able to hit even if JS code is stuck in the infinite loop.

실제로 이 방법으로 remy sharp는 무한루프에 빠진 지점을 찾았다고...

See Also

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment