Yukarıda cevaplandığı gibi, doğru cevap her şeyi VS2015 ile derlemektir, ancak ilgi için aşağıdaki benim problem analizimdir.
Bu sembol, Microsoft tarafından VS2015'in bir parçası olarak sağlanan herhangi bir statik kitaplıkta tanımlanmamış gibi görünmektedir; bu, diğerleri olduğu için oldukça tuhaftır. Nedenini keşfetmek için, bu işlevin bildirimine ve daha da önemlisi nasıl kullanıldığına bakmamız gerekir.
İşte Visual Studio 2008 üstbilgilerinden bir snippet:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Böylece, fonksiyonun işinin bir FILE nesnesi dizisinin başlangıcını döndürmek olduğunu görebiliriz (tutamaçlar değil, "FILE *" tutamaçtır, FILE, önemli durum güzelliklerini depolayan temel opak veri yapısıdır). Bu işlevin kullanıcıları, çeşitli fscanf, fprintf tarzı çağrılar için kullanılan üç makrodur. Stdin, stdout ve stderr.
Şimdi, Visual Studio 2015'in aynı şeyleri nasıl tanımladığına bir göz atalım:
_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))
Dolayısıyla, değiştirme işlevinin artık dosya nesneleri dizisinin adresi yerine dosya tutamacını döndürmesi için yaklaşım değişti ve makrolar, yalnızca bir tanımlayıcı numarada geçen işlevi çağırmak için değişti.
Öyleyse neden uyumlu bir API sağlayamıyoruz / biz sağlayamıyoruz? __İob_func aracılığıyla orijinal uygulamaları açısından Microsoft'un ihlal edemeyeceği iki temel kural vardır:
- Öncekiyle aynı şekilde dizine eklenebilen üç FILE yapısı dizisi olmalıdır.
- FILE’ın yapısal düzeni değiştirilemez.
Yukarıdakilerden herhangi birindeki herhangi bir değişiklik, buna bağlı mevcut derlenmiş kodun, bu API çağrılırsa çok yanlış olacağı anlamına gelir.
FILE'ın nasıl tanımlandığına bir göz atalım.
İlk olarak VS2008 FILE tanımı:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Ve şimdi VS2015 FILE tanımı:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
Yani işin püf noktası var: yapı şekil değiştirdi. __İob_func'a atıfta bulunan mevcut derlenmiş kod, döndürülen verilerin hem indekslenebilen bir dizi olmasına hem de bu dizideki öğelerin birbirinden aynı uzaklıkta olmasına dayanır.
Bu satırlar boyunca yukarıdaki cevaplarda bahsedilen olası çözümler, birkaç nedenden dolayı (çağrıldıysa) işe yaramayacaktır:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
FILE _iob dizisi VS2015 ile derlenecek ve bu nedenle bir void * içeren yapılar bloğu olarak yerleştirilecektir. 32 bitlik hizalama varsayıldığında, bu öğeler 4 bayt ayrı olacaktır. Dolayısıyla, _iob [0] ofset 0'da, _iob [1] ofset 4'te ve _iob [2] ofset 8'de. Çağrı kodu bunun yerine FILE'ın çok daha uzun olmasını, sistemimde 32 bayta hizalanmasını bekler, vb. döndürülen dizinin adresini alacak ve sıfır elemanına ulaşmak için 0 bayt ekleyecektir (bu tamamdır), ancak _iob [1] için 32 bayt eklemesi gerektiği sonucuna varacak ve _iob [2] için sonuç çıkaracaktır 64 bayt eklemesi gerektiğini (çünkü VS2008 başlıklarında böyle görünüyordu). Ve gerçekten de VS2008 için demonte edilen kod bunu gösteriyor.
Yukarıdaki çözümle ilgili ikincil bir sorun , FILE * tutamacının değil, FILE yapısının (* stdin) içeriğini kopyalamasıdır . Yani herhangi bir VS2008 kodu, VS2015'ten farklı bir temel yapıya bakıyor olacaktır. Yapı yalnızca işaretçiler içeriyorsa bu işe yarayabilir, ancak bu büyük bir risk. Her halükarda, ilk konu bunu ilgisiz kılar.
Hayal edebildiğim tek hack, __iob_func'un hangi gerçek dosya tutamacını aradıklarını bulmak için çağrı yığınını yürüttüğü (döndürülen adrese eklenen ofsete dayalı olarak) ve hesaplanan bir değer döndürdüğüdür. doğru cevabı verir. Bu, göründüğü kadar çılgınca ama sadece x86 için prototip (x64 değil) eğlenmeniz için aşağıda listelenmiştir. Deneylerimde iyi çalıştı, ancak kilometreniz değişebilir - üretim kullanımı için önerilmez!
#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;
}