위에서 대답했듯이 정답은 VS2015로 모든 것을 컴파일하는 것이지만 관심을 위해 다음은 문제에 대한 분석입니다.
이 기호는 Microsoft에서 VS2015의 일부로 제공하는 정적 라이브러리에 정의되어 있지 않은 것으로 보이며, 다른 모든 항목이 그렇기 때문에 다소 특이합니다. 그 이유를 알아 보려면 해당 함수의 선언과 더 중요한 것은 어떻게 사용되는지 살펴 봐야합니다.
다음은 Visual Studio 2008 헤더의 스 니펫입니다.
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
따라서 함수의 작업은 FILE 객체 배열의 시작을 반환하는 것임을 알 수 있습니다 (핸들이 아니라 "FILE *"이 핸들이고 FILE이 중요한 상태를 저장하는 기본 불투명 데이터 구조 임). 이 함수의 사용자는 다양한 fscanf, fprintf 스타일 호출에 사용되는 세 가지 매크로 stdin, stdout 및 stderr입니다.
이제 Visual Studio 2015에서 동일한 사항을 정의하는 방법을 살펴 보겠습니다.
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
따라서 대체 함수가 파일 객체 배열의 주소가 아닌 파일 핸들을 반환하도록 접근 방식이 변경되었으며 매크로는 단순히 식별 번호를 전달하는 함수를 호출하도록 변경되었습니다.
그렇다면 왜 그들은 호환 가능한 API를 제공 할 수 없습니까? __iob_func를 통한 원래 구현 측면에서 Microsoft가 위반할 수없는 두 가지 주요 규칙이 있습니다.
- 이전과 동일한 방식으로 인덱싱 할 수있는 세 개의 FILE 구조 배열이 있어야합니다.
- FILE의 구조적 레이아웃은 변경할 수 없습니다.
위의 내용 중 하나가 변경되면 해당 API가 호출 될 경우 연결된 기존 컴파일 된 코드가 심하게 잘못 될 수 있습니다.
FILE이 어떻게 정의되었는지 살펴 보겠습니다.
먼저 VS2008 FILE 정의 :
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
이제 VS2015 FILE 정의 :
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
그래서 그것의 핵심은 구조가 변화했습니다. __iob_func를 참조하는 기존의 컴파일 된 코드는 반환 된 데이터가 인덱싱 할 수있는 배열이고 해당 배열에서 요소가 같은 거리 떨어져 있다는 사실에 의존합니다.
위의 답변에서 언급 된 가능한 솔루션은 다음과 같은 몇 가지 이유로 작동하지 않습니다 (호출 된 경우).
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
FILE 배열 _iob는 VS2015로 컴파일되므로 void *를 포함하는 구조 블록으로 배치됩니다. 32 비트 정렬을 가정하면 이러한 요소는 4 바이트 떨어져 있습니다. 따라서 _iob [0]은 오프셋 0, _iob [1]은 오프셋 4, _iob [2]는 오프셋 8에 있습니다. 대신 호출 코드는 FILE이 훨씬 더 길고 시스템에서 32 바이트로 정렬 될 것으로 예상합니다. 반환 된 배열의 주소를 가져와 0 바이트를 추가하여 요소 0 (괜찮음)에 도달하지만 _iob [1]의 경우 32 바이트를 추가해야한다고 추론하고 _iob [2]에 대해서는 추론합니다. 64 바이트를 추가해야한다는 것입니다 (VS2008 헤더에서보기 때문에). 그리고 실제로 VS2008의 디스 어셈블 된 코드는 이것을 보여줍니다.
위 솔루션의 두 번째 문제 는 FILE * 핸들이 아니라 FILE 구조 (* stdin)의 내용을 복사 한다는 것 입니다. 따라서 모든 VS2008 코드는 VS2015와 다른 기본 구조를 보게됩니다. 구조에 포인터 만 포함 된 경우 작동 할 수 있지만 큰 위험입니다. 어쨌든 첫 번째 문제는 이것을 무의미하게 만듭니다.
내가 꿈꿀 수 있었던 유일한 해킹은 __iob_func가 호출 스택을 통해 그들이 찾고있는 실제 파일 핸들 (반환 된 주소에 추가 된 오프셋을 기반으로)을 알아 내고 다음과 같은 계산 된 값을 반환하는 것입니다. 정답을 제공합니다. 이것은 들리는 것만 큼 미쳤지 만 x86 전용 프로토 타입 (x64가 아님)은 아래에 나열되어 있습니다. 내 실험에서는 제대로 작동했지만 마일리지가 다를 수 있습니다. 프로덕션 용도로는 권장되지 않습니다!
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
/* #define LOG */
#if defined(_M_IX86)
#define GET_CURRENT_CONTEXT(c, contextFlags) \
do { \
c.ContextFlags = contextFlags; \
__asm call x \
__asm x: pop eax \
__asm mov c.Eip, eax \
__asm mov c.Ebp, ebp \
__asm mov c.Esp, esp \
} while(0);
#else
/* This should work for 64-bit apps, but doesn't */
#define GET_CURRENT_CONTEXT(c, contextFlags) \
do { \
c.ContextFlags = contextFlags; \
RtlCaptureContext(&c); \
} while(0);
#endif
FILE * __cdecl __iob_func(void)
{
CONTEXT c = { 0 };
STACKFRAME64 s = { 0 };
DWORD imageType;
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
GET_CURRENT_CONTEXT(c, CONTEXT_FULL);
#ifdef _M_IX86
imageType = IMAGE_FILE_MACHINE_I386;
s.AddrPC.Offset = c.Eip;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.Ebp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrStack.Offset = c.Esp;
s.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
imageType = IMAGE_FILE_MACHINE_AMD64;
s.AddrPC.Offset = c.Rip;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.Rsp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrStack.Offset = c.Rsp;
s.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
imageType = IMAGE_FILE_MACHINE_IA64;
s.AddrPC.Offset = c.StIIP;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.IntSp;
s.AddrFrame.Mode = AddrModeFlat;
s.AddrBStore.Offset = c.RsBSP;
s.AddrBStore.Mode = AddrModeFlat;
s.AddrStack.Offset = c.IntSp;
s.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif
if (!StackWalk64(imageType, hProcess, hThread, &s, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
{
#ifdef LOG
printf("Error: 0x%08X (Address: %p)\n", GetLastError(), (LPVOID)s.AddrPC.Offset);
#endif
return NULL;
}
if (s.AddrReturn.Offset == 0)
{
return NULL;
}
{
unsigned char const * assembly = (unsigned char const *)(s.AddrReturn.Offset);
#ifdef LOG
printf("Code bytes proceeding call to __iob_func: %p: %02X,%02X,%02X\n", assembly, *assembly, *(assembly + 1), *(assembly + 2));
#endif
if (*assembly == 0x83 && *(assembly + 1) == 0xC0 && (*(assembly + 2) == 0x20 || *(assembly + 2) == 0x40))
{
if (*(assembly + 2) == 32)
{
return (FILE*)((unsigned char *)stdout - 32);
}
if (*(assembly + 2) == 64)
{
return (FILE*)((unsigned char *)stderr - 64);
}
}
else
{
return stdin;
}
}
return NULL;
}