[project] Node.js Happy Hacking 두번째 이야기

happy hacking node.js - 두번째 이야기

[cookbook] happy hacking node.js - 첫번째 이야기 에 이어서 두번재 이야기를 적어봅니다. 이야기를 이어가기 전에 첫번째 이야기에서는 아래 내용에 대해서 알아보았습니다.

이번에 다룰 내용은 아래와 같습니다.

node.js의 프로그래밍 언어구성

node.js는 총 4개의 프로그래밍 언어로 구성되어 있습니다.

node.js의 프로그래밍에 관여하는 언어는 python을 제외한 3가지이며, 특히 javascript로 작성된 native module을 CPP로 개발된 v8 엔진에 접목하는 과정은 복잡하기도 하지만, 창시자의 노력이 엿보이는 부분입니다. 그럼 javascript로 작성된 native module이 어떻게 v8 엔진에서 컴파일 될 수 있는지 알아보겠습니다.

v8 utility JS2C

v8의 도구로 존재 하고, WAF build tool (wscript)에 import되어 make build시점에 실행되며, Javascript 소스를 C 소스로 변경해주는 유틸리티입니다. 이것은 node.js 내부에서 javascript 로 작성된 native module을 v8에 컴파일하기 위한 기초적인 도구입니다. javascript module을 C style의 문자열 배열 형태로 변경해주고 헤더(.h)파일을 생성하며, 이것은 node_javascript.cc에서 include되어 v8으로 컴파일 되는 인터페이스를 제공하게 됩니다. 그럼 소스를 통해서 알아보겠습니다.

사용방법

import js2c

# javascript 소스코드, 여러개 지정
source = ['./lib/run.js']

# output 해더파일
targets = ['output.h']

# JS2C 함수에 source, targets 전달
# 결과값 없음.
js2c.JS2C(source, targets)

예제

nodejs 소스트리에서 tools 디렉토리안에 js2c module을 찾을 수 있습니다. 작성 예제소스는 아래와 같습니다.

소스작성

$ vi make_node_natives.py

import sys

# 최상위 디렉토리에서 tools를 라이브러리 참조디렉토리로 포함
sys.path.append('./tools')

# js2c 불러옴
import js2c

# 소스트리 최상위에서 native module이 존재하는 lib 디렉토리안의
# console.js 지정
source = ['./lib/console.js']
targets = ['output.h']
js2c.JS2C(source, targets)

실행

아래 명령을 실행하면 output.h 헤더파일이 생성됩니다.

$ python make_node_natives.py    

헤더파일 (output.h) 결과내용

#define node_natives_h

// 기본적으로 node 네임스페이스 지정됨
namespace node {

    .....
    // 이 부분이 C 소스코드의 문자열 배열형태입니다.
    const char console_native[] = { 47, 47, 32, 67, 111 ...."중략"..... 125, 10, 125, 59, 10, 0 };
    .....

    // name, source, len으로 구성되어진 구조체선언
    struct _native {
        const char* name;
        const char* source;
        size_t source_len;
    };

    // 위 구조체를 지정하여
    static const struct _native natives[] = {

        // 아래 내용들을 natives[0].name, natives[0].source .. 으로
        // 접근할 수 있음.
        { "console", console_native, sizeof(console_native)-1 },

        { NULL, NULL } /* sentinel */

    };
    ....
    ....

js2c module로 인해 헤더파일로 작성되는 과정을 살펴보았습니다. 이외 헤더파일을 include하여 v8내부에 컴파일하는 과정이 있는데, 여기서는 설명하지 않겠습니다. 이것은 그리 중요하지 않으며, v8이 javascript를 컴파일 하는 역할을 담당한다 라고만 인지해도 될것입니다. 그리고, javascript 소스가 cpp 에서 인식되는 이 과정을 기억하시기 바랍니다.

output.h 의 내용을 decompile해보기

위에 설명했듯이 js2c로 컴파일하면 숫자로 이루어진 값이 문자열 배열형태에 선언됩니다. 과연 이 숫자가 무엇일까하는 궁금증이 나올수 있기 때문에, 한번 decompile을 해보겠습니다.

소스코드

js2c module의 결과값인 헤더파일에서 문자열 배열로 선언된 "const char console_native= .... " 이 부분을 복사하여 C 에서 간단하게 확인해보겠습니다.

# vi convert_node_natives.c

#include <stdio.h>
int main(void)
{
   // native module console 객체가 js2c로 인해 변화된 값
   const char console_native[] = { 47, 47, 32, 67, ... 중략 ... 125, 10, 125, 59, 10, 0 };

   // 포인터 선언
   const char *pp;
   pp = console_native;

   while (*pp)
   {
          printf("%c", *pp);
          pp++;
   }

   return 0;
}

컴파일

$ gcc convert_node_natives.c -o exec


- 실행
$ ./exec| head
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//

head 만 보아도 무슨 문구인지 알거 같습니다. lib/console.js 파일을 보시면 상단에 작성된 카피라이트 부분입니다.

$ ./exec
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var util = require('util');
var styles = {
        'cyan'      : ['\\033[36m', '\\033[39m']
};
exports.log = function() {
  process.stdout.write(util.format.apply(this, arguments) + '\\ n');
};

exports.info = exports.log;

exports.warn = function() {
  process.stderr.write(util.format.apply(this, arguments) + '\\ n');
};


exports.error = exports.warn;


exports.dir = function(object) {
  process.stdout.write(util.inspect(object) + '\\ n');
};


var times = {};
exports.time = function(label) {
  times[label] = Date.now();
};


exports.timeEnd = function(label) {
  var duration = Date.now() - times[label];
  exports.log('%s: %dms', label, duration);
};


exports.trace = function(label) {
  // TODO probably can to do this better with V8's debug object once that is
  // exposed.
  var err = new Error;
  err.name = 'Trace';
  err.message = label || '';
  Error.captureStackTrace(err, arguments.callee);
  console.error(err.stack);
};


exports.assert = function(expression) {
  if (!expression) {
    var arr = Array.prototype.slice.call(arguments, 1);
    require('assert').ok(false, util.format.apply(this, arr));
  }
};

자 이런방법으로 js2c에 의해 만들어진 C 헤더파일은 CPP에서 인식되어 처리될 수 있는 인터페이스를 제공하게 됩니다.

결론

Node.js Black Edition을 제작하는 과정에서의 분석내용들을 모두 기술했습니다. cpp module 탑재과정도 있으나 생략하겠습니다. 아무튼 Node의 노이즈가 계속 발생할 수 있는 환경이 되었으면 좋겠네요. 감사합니다.

추천 관련글