C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기
예전에 한번 이에 대해 다룬 글이 있습니다.
C++의 inline asm 사용을 .NET으로 포팅하는 방법
; https://www.sysnet.pe.kr/2/0/1267
근데, 좀 번잡하군요. ^^; 가상 메모리 할당에 실행 권한까지 변경하는 작업이 너무 복잡합니다. 좀 더 간단하게 할 수 있을까요?
생각해 보니, 변경될 어셈블리 코드를 담을 만큼의 충분한 메서드 크기만 보장된다면 차라리 그것을 재활용하는 방법이 더 나을 듯 합니다. "
C++의 inline asm 사용을 .NET으로 포팅하는 방법" 글에서 예제로 든 CpuId 값을 구하는 방법을 예로 들어볼까요? ^^
일단, dummy 용도의 메서드를 하나 만드는데요. 크기는 대충 cpuid 코드를 실행할 정도만 안전하게 포함할수만 있으면 됩니다.
public unsafe static void CpuIdInfo(byte *ptr)
{
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
Console.WriteLine(0);
Console.WriteLine(1);
}
그다음 cpuid 호출을 하는 기계어 코드를 다음과 같이 마련해 주고,
private readonly static byte[] x86CpuIdBytes =
{
0x55,
0x8B, 0xEC,
0x53,
0x57,
0x33, 0xDB,
0x8B, 0xF9, // mov edi,ecx ; stdcall 호출 관행으로 불리기 때문에 ecx에 첫 번째 인자값이 들어 있음.
0x33, 0xD2,
0xB8, 0x00, 0x00, 0x00, 0x00,
0x0F, 0xA2, // cpuid
// 0x8B, 0x7D, 0x08, ; cdecl 호출 관행인 경우 필요한 것으로 stdcall인 경우 필요없음.
0x89, 0x07,
0x89, 0x5F, 0x04,
0x89, 0x4F, 0x08,
0x89, 0x57, 0x0C,
0x5F,
0x5B,
0x5D,
0xC3, // ret
};
다음과 같이 GetFunctionPointer로 반환받은 위치에 기계어 코드를 덮어써주면 됩니다.
// 주의: x86 코드에서만 동작!!!!
static unsafe void Main(string[] args)
{
byte[] cpuIdBytes = new byte[4 * 4];
fixed(byte* idPtr = cpuIdBytes)
{
// CpuIdInfo(idPtr);
RuntimeMethodHandle mh = typeof(Program).GetMethod("CpuIdInfo", BindingFlags.Static | BindingFlags.Public).MethodHandle;
RuntimeHelpers.PrepareMethod(mh);
IntPtr ptr = mh.GetFunctionPointer();
int offset;
byte* baseCodes = (byte*)ptr.ToPointer();
byte* asmCodes;
if (Debugger.IsAttached == true)
{
offset = *(int*)(baseCodes + 1);
asmCodes = baseCodes + offset + 5; // 5byte == 0xe9 xx xx xx xx
}
else
{
asmCodes = baseCodes;
}
offset = 0;
foreach (byte aByte in x86CpuIdBytes)
{
*(asmCodes + offset) = aByte;
offset++;
}
CpuIdInfo(idPtr);
}
Console.WriteLine("Cpu Id: " + BitConverter.ToString(cpuIdBytes));
}
훨씬 간단하긴 하지만, GetFunctionPointer가 반환하는 기계어 코드가 디버거 유무에 따라 달라진다는 점과 5바이트 옵셋값이 하드코딩된다는 점이 좀 그렇긴 합니다.
어쨌든, 원리를 알아두는 차원에서 그냥 읽고 이해하기만 하면 될 듯! ^^
(
첨부한 코드는 위의 실습 프로젝트입니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]