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 인 것 같은데 나머지 대응 되는 녀석은 잘 모르겠습니다. [이런 상황입니다. ㅠㅠ ]
문서와 소스코드를 첨부합니다. 질문이 너무 길어서 죄송합니다..ㅎㅎ
| 첨부 | 파일 크기 |
|---|---|
| 51.21 KB | |
| 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 모듈을 짜주는 방식으로 선회하였습니다.
댓글 달기