C의 인라인 어셈블리에 대해서 궁금한 점이 있습니다.

poniard의 이미지

안녕하세요. 저는 자바 프로그래머입니다. C에 대해서는 교양 수준으로만 알고 있고요.. :oops:

사정이 있어서 JNI 프로그래밍을 하고 있는데..
썬의 JNI 프로그래밍 스펙 & 프로그래머 가이드를 보면 JNI 프로그램을 하는데 2가지 방식이 있습니다.

하나는 기존에 익히 알고 계시는 것 처럼 클래스 안에 네이티브 메소드를 선언하고 javah를 사용해서 헤더 파일을 만든 후에 네이티브 메소드에 대응하는 C 또는 C++ 함수를 만들어서 컴파일하고 사용합니다. 아래 코드 처럼요..

class HelloWorld {
    private native void print();
    public static void main(String[] args) {
        new HelloWorld().print();
    }
    static {
        System.loadLibrary("HelloWorld");
    }
}

#include "HelloWorld.h"

JNIEXPORT void JNICALL 
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
    printf("Hello World!\n");
    return;
}

다른 한 가지 방식이 Shared Stub 방식이라는 것인데..
매번 네이티브 메소드와 대응하는 C/C++ 코드를 만들어서 컴파일 하는 것이 아니라 C/C++ 라이브러리를 대신 호출하고 그 결과를 자바 클래스에 돌려주는 방식입니다.
자바 프로그래머는 로드할 라이브러리 이름과 메소드의 심볼? 만 알고 있으면 쉽게 호출 할 수가 있습니다.

다음과 같은 형식입니다.

    private static CFunction c_atol =
        new CFunction("msvcrt.dll", // native library name
                      "atol",       // C function name
                      "C");         // calling convention
    public static int atol(String str) {
        return c_atol.callInt(new Object[] {str});
    }

위 코드에서 CFunction 클래스가 C/C++의 함수 하나를 로드해서 callInt 하면 int 형을 리턴하는 구조인데요.

CFunction 클래스 안에 네이티브 메소드가 매우 많이 있습니다. 이 네이티브 메소드와 대응하는 cpp, c 파일들이 Stub인데요.

 WIN32용 dispatch_86.c
#ifdef JNI_BOOK

int asm_dispatch_int(void *func,  // pointer to the C function
                     int nwords,  // number of words in args array
                     long *args,  // start of the argument data
                     int conv)    // calling convention 0: C
                                  //                    1: JNI
{
    __asm {
        mov esi, args
        mov edx, nwords
        // word address -> byte address
        shl edx, 2
        sub edx, 4
        jc  args_done

        // push the last argument first
    args_loop:
        mov eax, DWORD PTR [esi+edx]
        push eax
        sub edx, 4
        jge SHORT args_loop

    args_done:
        call func

        // check for calling convention
        mov edx, conv
        or edx, edx
        jnz jni_call

        // pop the arguments
        mov edx, nwords
        shl edx, 2
        add esp, edx
    jni_call:
        // done, return value in eax
    }
}

#endif /* JNI_BOOK */

/*
 * Copies the arguments from the given array to C stack, invoke the
 * target function, and copy the result back.
 */
void asm_dispatch(void *func,
		  int nwords,
		  char *arg_types,
		  long *args,
		  int res_type,
		  long *resP,
		  int conv)
{
    __asm {

	mov esi, args
	mov edx, nwords
	// word address -> byte address
	shl edx, 2

        sub edx, 4
	jc  args_done

	// Push the last argument first.
    args_loop:
	mov eax, DWORD PTR [esi+edx]
	push eax
	sub edx, 4
	jge SHORT args_loop

    args_done:
	call func

	mov edx, conv
        or edx, edx
	jnz is_stdcall

	// pop arguments
	mov edx, nwords
        shl edx, 2
        add esp, edx

is_stdcall:
        mov esi, resP
	mov edx, res_type
	dec edx
	jge not_p64

	// p64
	mov [esi], eax
	mov [esi+4], 0
	jmp done

    not_p64:
	dec edx
        jge not_i32

	// i32
	mov [esi], eax
        jmp done

    not_i32:
	dec edx
        jge not_f32

        // f32
	fstp DWORD PTR [esi]
	jmp done

    not_f32:
        // f64
	fstp QWORD PTR [esi]

    done:
    }
}

dispatch.cpp
#ifdef SOLARIS2
#include <dlfcn.h>
#define LOAD_LIBRARY(name) dlopen(name, RTLD_LAZY)
#define FIND_ENTRY(lib, name) dlsym(lib, name)
#endif

#ifdef WIN32
#include <windows.h>
#define LOAD_LIBRARY(name) LoadLibrary(name)
#define FIND_ENTRY(lib, name) GetProcAddress(lib, name)
#endif

#include <stdlib.h>
#include <string.h>

#include <jni.h>

#include "CPointer.h"
#include "CFunction.h"
#include "CMalloc.h"

/* Global references to frequently used classes and objects */
static jclass Class_String;
static jclass Class_Integer;
static jclass Class_Float;
static jclass Class_Double;
static jclass Class_CPointer;

/* Cached field and method IDs */
static jmethodID MID_String_getBytes;
static jmethodID MID_String_init;

static jfieldID FID_Integer_value;
static jfieldID FID_Float_value;
static jfieldID FID_Double_value;
static jfieldID FID_CPointer_peer;
static jfieldID FID_CFunction_conv;

/* Forward declarations */
static void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg);
static char * JNU_GetStringNativeChars(JNIEnv *env, jstring jstr);
static jstring JNU_NewStringNative(JNIEnv *env, const char *str);
static jobject makeCPointer(JNIEnv *env, void *p);


/********************************************************************/
/*		     Native methods of class CFunction		    */
/********************************************************************/

/* These are the set of types CFunction can handle now */
typedef enum {
    TY_CPTR = 0,
    TY_INTEGER,
    TY_FLOAT,
    TY_DOUBLE,
    TY_DOUBLE2,
    TY_STRING
} ty_t;

/* represent a machine word */
typedef union {
    jint i;
    jfloat f;
    void *p;
} word_t;

/* A CPU-dependent assembly routine that passes the arguments to C
 * stack and invoke the function.
 */
extern "C" void 
asm_dispatch(void *func,
	     int nwords,
	     char *args_types,
	     word_t *args,
	     ty_t res_type,
	     word_t *resP,
	     int conv);

/* invoke the real native function */
static void
dispatch(JNIEnv *env,
	 jobject self,
	 jobjectArray arr,
	 ty_t res_ty,
	 jvalue *resP)
{
#define MAX_NARGS 32
    int i, nargs, nwords;
    void *func;
    char argTypes[MAX_NARGS];
    word_t c_args[MAX_NARGS * 2];
    int conv;

    nargs = env->GetArrayLength(arr);
    if (nargs > MAX_NARGS) {
        JNU_ThrowByName(env, "java/lang/IllegalArgumentException",
		    "too many arguments");
	return;
    }
    func = (void *)env->GetLongField(self, FID_CPointer_peer);

    for (nwords = 0, i = 0; i < nargs; i++) {
        jobject arg = env->GetObjectArrayElement(arr, i);
	if (arg == NULL) {
	    c_args[nwords].p = NULL;
	    argTypes[nwords++] = TY_CPTR;
	} else if (env->IsInstanceOf(arg, Class_Integer)) {
	    c_args[nwords].i =
	        env->GetIntField(arg, FID_Integer_value);
	    argTypes[nwords++] = TY_INTEGER;
	} else if (env->IsInstanceOf(arg, Class_CPointer)) {
	    c_args[nwords].p = 
	        (void *)env->GetLongField(arg, FID_CPointer_peer);
	    argTypes[nwords++] = TY_CPTR;
	} else if (env->IsInstanceOf(arg, Class_String)) {
	    if ((c_args[nwords].p = JNU_GetStringNativeChars(env, (jstring)arg)) == 0) {
	        goto cleanup;
	    }
	    argTypes[nwords++] = TY_STRING;
	} else if (env->IsInstanceOf(arg, Class_Float)) {
	    c_args[nwords].f =
	        env->GetFloatField(arg, FID_Float_value);
	    argTypes[nwords++] = TY_FLOAT;
	} else if (env->IsInstanceOf(arg, Class_Double)) {
	    *(jdouble *)(c_args + nwords) = 
	        env->GetDoubleField(arg, FID_Double_value);
	    argTypes[nwords] = TY_DOUBLE;
	    /* harmless with 64-bit machines*/
	    argTypes[nwords + 1] = TY_DOUBLE2;
	    /* make sure things work on 64-bit machines */
	    nwords += sizeof(jdouble) / sizeof(word_t);
	} else {
	    JNU_ThrowByName(env, "java/lang/IllegalArgumentException",
			"unrecognized argument type");
	    goto cleanup;
	}
	env->DeleteLocalRef(arg);
    }

    conv = env->GetIntField(self, FID_CFunction_conv);
    asm_dispatch(func, nwords, argTypes, c_args, res_ty, (word_t *)resP, conv);
...
...
...
// 내용 생략
}

내용은 위와 같습니다. 가이드에는 solaris용과 win32용 이 제공되는 데 이 녀석들을 리눅스나 여타 unix 계열에서도 사용을 해보는게 제 목적입니다. 즉, jni 프로그래밍에 드는 귀찮음을 한 방에 해결하는 것이죠. (물론 장단점이 있지만 말입니다.)

어쨌든 Win32 플랫폼에서는 잘 작동을 합니다. 제가 만든 dll 파일도 잘 불러오고요. 그런데 dispatch_x86.c 안에 들어있는 인라인 어셈블리를 리눅스에 옮기려다 보니 어셈블리 문법이 AT&T 문법이 아니라 gcc에서 컴파일이 안되는 것 같더군요.

궁금한 점은 저 인라인 어셈블리 문법을 AT&T 형식으로 변환하면 다른 unix 계열에서도 gcc를 이용하여 컴파일할 수 있는지 입니다.
한 플랫폼에서 한 번씩만 컴파일해 주고 사용하면 참 괜찮을 듯 한데 말이죠.

제가 위에서 말씀드렸다시피 C는 교양수준이고 어셈블리는 까막눈 수준이라서요.. :oops: 가능한 작업인지의 여부를 알고 싶답니다. 덤으로 만약 위 작업이 가능하다면 AT&T 문법과 INTEL 문법의 비교표 같은 자료를 어디서 찾을 수 있는지 가르쳐주시면 고맙구요.. :)

mov가 movl 인 것 같은데 나머지 대응 되는 녀석은 잘 모르겠습니다. [이런 상황입니다. ㅠㅠ ]

문서와 소스코드를 첨부합니다. 질문이 너무 길어서 죄송합니다..ㅎㅎ

File attachments: 
첨부파일 크기
Package icon jniexamples.zip51.21 KB
Package icon jni.zip2.31 MB
익명 사용자의 이미지

...

kslee80의 이미지

매우 어려운 작업이 될 것입니다.

일단 x86 용으로 보이는 dispatch_x86.c 만 해도
해당 코드가 gcc 에서 컴파일이 되지 않는 이유는 인라인 어셈블리가 AT&T 형식이 아니여서가 아니라
asm 키워드의 사용법 자체가 gcc 의 asm 키워드 사용법과 완전히 다릅니다.
이 부분은 gcc 의 인라인 어셈블리 문서를 찾아서 보시면 완전히 틀리다는것을 알 수 있을 것입니다.
(gcc 인라인 어셈블리 문서는 KLDP Wiki 를 검색하면 쉽게 찾을수 있습니다)
(그나저나 좀 눈에 익은 asm 키워드의 사용법이군요...예전 DOS 시절에 Turbo C 에서 사용했던 인라인 어셈과 비슷한듯...?)

게다가, 다른 unix 시스템이라고 말씀하셨는데...
그 unix 시스템에서 사용하는 CPU 가 x86 계열이 아니라면
이 dispatch_x86.c 는 아예 사용할 수 없습니다.
해당 CPU 에 맞춰서 저 내용을 새로 짜야 합니다.
어셈블리는 컴파일러에 따라서 문법이 틀린게 아니라, CPU 에 따라서 틀린 것입니다.
(실제로는 어셈블러에 의존적이지만, 같은 CPU 에 맞는 기계어를 생성해내는 어셈블러들이 서로 다른 키워드를 사용하는 경우는 별로 없습니다)

그리고 마지막으로,
x86 용 어셈블러중 쉽게 접하게 되는것은 AT&T 문법을 사용하는 어셈블러와
MS 의 문법(단지 MS 에서 만든 어셈블러만 사용하는 문법이라 이렇게 부릅니다)을 사용하는 어셈블러가 있습니다.
두 문법의 차이는 operand 순서와 register 명명법등이 틀리며,
movl 같은 것은 같습니다.(부르는 이름을 까먹었네요 -.-)

poniard의 이미지

넵. 감사합니다.
실은 KLDP Wiki의 인라인 어셈블러 문서를 참고해서 일을 진행하다가 투입되는 노력에 비해 얻을 수 있는 결과가 너무 적은 것 같다는 의견을 냈습니다.

말씀하신 것처럼 x86이외의 계열 CPU에서 안정적으로 작동하는 코드를 보장할 수도 없었구요..^^

그래서, JNI 가이드 문서를 요약 제공하였고, 후에 필요할 시 JNI 모듈을 짜주는 방식으로 선회하였습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.