Emscripten을 사용하여 브라우저에서 네이티브 C/C++ 코드 실행

ACPI WMI 블록을 분석하는 간단한 도구인 WMIDumpper 을 작업하는 동안 JavaScript에서 bmfdec을 구현하는 방법을 알아내야 했습니다. 내 첫 번째 생각은 그것을 JavaScript로 포팅하고 시간과 노력을 들여 JS에서 ~1500줄의 C 코드를 다시 작성하는 것이었습니다. 하지만 내 머릿속에 전구가 켜졌습니다. WebAssembly! 빠른 검색 결과 Emscripten이 정확히 내가 필요한 것으로 나타났습니다. C/C++ 네이티브 코드를 WebAssembly로 컴파일하고 웹에서 실행할 수 있습니다.

WebAssembly/JS에 네이티브 코드 빌드



bmfdec을 복제하고 프로젝트를 wasm로 컴파일하는 방법에 대해 Emscriptendocumentation을 따랐습니다. 건물bmfdec은 Emscripten 컴파일러를 사용하여 간단했습니다.

emcc bmfdec/bmf2mof.c -o bmf2mof.js



하지만 한 가지 문제가 있었는데, bmfdec은 stdin에서 이진 파일로 입력을 받도록 작성되었습니다. bmfdec.c를 수정하고 main() 함수를 변경하여 stdin에서 바이너리를 읽는 대신 버퍼를 가져와서 parse_data()라고 불렀습니다.

int parse_data(uint8_t *pin, ssize_t lin) {
  static char pout[0x40000];
  int lout;
  if (lin < 0) {
    fprintf(stderr, "Failed to read data: %s\n", strerror(errno));
    return 1;
  } else if (lin == sizeof(pin)) {
    fprintf(stderr, "Failed to read data: %s\n", strerror(EFBIG));
    return 1;
  }
  if (lin <= 16 || ((uint32_t*)pin)[0] != 0x424D4F46 || ((uint32_t*)pin)[1] != 0x01 || ((uint32_t*)pin)[2] != (uint32_t)lin-16 || ((uint32_t*)pin)[3] > sizeof(pout)) {
    fprintf(stderr, "Invalid input\n");
    return 1;
  }
  lout = ((uint32_t*)pin)[3];
  if (ds_dec((char *)pin+16, lin-16, pout, lout, 0) != lout) {
    fprintf(stderr, "Decompress failed\n");
    return 1;
  }
  return process_data(pout, lout);
}



네이티브 함수를 JavaScript로 내보내기



JavaScript 내에서 C 함수를 호출할 수 있으려면 일부 추가 플래그로 컴파일bmf2mof하여 모듈화하고 심볼을 JS 출력 파일로 내보내야 합니다.
MODULARIZE 컴파일러 플래그를 사용하면 생성된 JavaScript가 Node.js에서 약속 및 require()를 사용할 수 있는 모듈식으로 만들어집니다. EXPORT_NAME='bmf2mof' 컴파일러 플래그는 내보낸 모듈 이름을 변경합니다. 이 경우 이름은 bmf2mof() 입니다. WASM=1는 wasm 출력을 원한다고 지정합니다. 마지막으로 "EXPORTED_FUNCTIONS=['_parse_data']"는 C 코드에서 함수parse_data를 내보냅니다. 우리는 또한 optimize 출력 JS 코드를 원하므로 -O2 를 사용할 것입니다.

emcc bmfdec/bmf2mof.c -s "EXPORTED_FUNCTIONS=['_parse_data']" -s "MODULARIZE=1" -s "EXPORT_NAME='bmf2mof'" -s "WASM=1" -O2 -o bmf2mof.js



이제 생성된 bmf2mof.js에는 C 함수에 매핑되고 JavaScript 코드에서 호출할 수 있는 _parse_data 함수가 있습니다.

const bmf2mof = require('bmf2mof.js')

const buf = new Uint8Array([0x46, 0x4F, 0x4D, 0x42, ..., 0x20, 0xEC, 0xFF, 0x0F])

bmf2mof().then(instance => {
    function arrayToPtr(array) {
        var ptr = instance._malloc(array.length)
        instance.HEAPU8.set(array, ptr)
        return ptr
    }

    instance._parse_data(arrayToPtr(buf), buf.length)
})



모든 것이 예상대로 작동하고 위의 코드node [filename].js를 실행하면 데이터가 표준 출력으로 출력됩니다. 그러나 이것을 브라우저에서 사용하려는 경우 출력이 콘솔로 이동합니다. 따라서 출력을 포착하고 원하는 위치로 리디렉션하는 방법을 찾아야 합니다. 이 경우에는 WMIDumpper 의 텍스트 영역이 됩니다.

생성된 JS 파일에 관해서는 Emscripten 문서가 약간 부족했기 때문에 그것이 무엇을 하는지 이해하기 위해 시간을 들였습니다. 생성된 JS는 C/C++에서 JS로의 매핑을 처리하기 위해 몇 가지 기본 함수를 정의하는 것으로 나타났습니다. 예를 들어, printErr에 바인딩하는 console.warn 함수를 정의합니다. 이는 stderr로 인쇄하는 C/C++ 코드가 JS에서 console.warn를 사용할 것임을 의미합니다. 자세한 기술 정보는 Create the Module object을 참조하십시오.

생성된 JS는 기본 모듈 기능을 재정의하는 객체를 사용합니다. 출력을 stdout에서 HTML의 텍스트 영역으로 리디렉션하려면 자체 함수print를 정의하기만 하면 됩니다.

const textarea = document.getElementById('textarea')

bmf2mof({
    print: function (text) {
        textarea.value += text + '\n'
    }
}).then(instance => ...)



물론 브라우저에서 이 코드를 사용하려면 먼저 HTML에서 생성된 JS 스크립트를 소싱해야 합니다.

참조



bmfdec에서 bmf2mof.wasm의 WebAssembly 분기 버전을 찾을 수 있습니다.

WMIDumpperwmidumpbmfdec의 웹 클론일 뿐이므로 iksaifpali의 멋진 작업에 찬사를 보냅니다.

좋은 웹페이지 즐겨찾기