C의 인라인 어셈블리에 대해서 궁금한 점이 있습니다.
안녕하세요. 저는 자바 프로그래머입니다. 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 인 것 같은데 나머지 대응 되는 녀석은 잘 모르겠습니다. [이런 상황입니다. ㅠㅠ ]
문서와 소스코드를 첨부합니다. 질문이 너무 길어서 죄송합니다..ㅎㅎ
첨부 | 파일 크기 |
---|---|
jniexamples.zip | 51.21 KB |
jni.zip | 2.31 MB |
...
...
매우 어려운 작업이 될 것입니다.일단 x86 용으로 보이는 dis
매우 어려운 작업이 될 것입니다.
일단 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 같은 것은 같습니다.(부르는 이름을 까먹었네요 -.-)
넵. 감사합니다.실은 KLDP Wiki의 인라인 어셈블러 문서를 참고
넵. 감사합니다.
실은 KLDP Wiki의 인라인 어셈블러 문서를 참고해서 일을 진행하다가 투입되는 노력에 비해 얻을 수 있는 결과가 너무 적은 것 같다는 의견을 냈습니다.
말씀하신 것처럼 x86이외의 계열 CPU에서 안정적으로 작동하는 코드를 보장할 수도 없었구요..^^
그래서, JNI 가이드 문서를 요약 제공하였고, 후에 필요할 시 JNI 모듈을 짜주는 방식으로 선회하였습니다.
댓글 달기