Jak odpowiedziałem powyżej, właściwą odpowiedzią jest skompilowanie wszystkiego z VS2015, ale dla zainteresowania poniżej znajduje się moja analiza problemu.
Wydaje się, że ten symbol nie jest zdefiniowany w żadnej statycznej bibliotece dostarczonej przez Microsoft w ramach VS2015, co jest dość osobliwe, ponieważ wszystkie inne są. Aby dowiedzieć się, dlaczego, musimy przyjrzeć się deklaracji tej funkcji i, co ważniejsze, jak jest używana.
Oto fragment z nagłówków programu Visual Studio 2008:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Widzimy więc, że zadaniem funkcji jest zwrócenie początku tablicy obiektów PLIKU (nie uchwytów, „PLIK *” jest uchwytem, PLIK jest podstawową nieprzezroczystą strukturą danych przechowującą ważne elementy stanu). Użytkownikami tej funkcji są trzy makra stdin, stdout i stderr, które są używane do różnych wywołań w stylu fscanf, fprintf.
Przyjrzyjmy się teraz, jak Visual Studio 2015 definiuje te same rzeczy:
_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))
Tak więc podejście zmieniło się, aby funkcja zastępująca zwracała teraz uchwyt pliku, a nie adres tablicy obiektów plików, a makra zmieniły się, aby po prostu wywołać funkcję przekazującą numer identyfikacyjny.
Dlaczego więc nie mogą / my zapewnić kompatybilnego interfejsu API? Istnieją dwie kluczowe zasady, których Microsoft nie może naruszać, jeśli chodzi o ich pierwotną implementację za pośrednictwem __iob_func:
- Musi istnieć tablica trzech struktur PLIKÓW, które mogą być indeksowane w taki sam sposób jak poprzednio.
- Układ strukturalny PLIKU nie może się zmienić.
Jakakolwiek zmiana w jednym z powyższych oznaczałaby istniejący skompilowany kod powiązany z tym, który poszedłby źle, gdyby wywołano ten interfejs API.
Przyjrzyjmy się, jak zdefiniowano / jest zdefiniowany PLIK.
Najpierw definicja pliku VS2008:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
A teraz definicja pliku VS2015:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
A więc sedno tego: struktura zmieniła kształt. Istniejący skompilowany kod odwołujący się do __iob_func opiera się na fakcie, że zwracane dane są zarówno tablicą, która może być indeksowana, jak i że w tej tablicy elementy są w tej samej odległości od siebie.
Możliwe rozwiązania wymienione w powyższych odpowiedziach nie zadziałałyby (gdyby zostały wywołane) z kilku powodów:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
Tablica FILE _iob zostanie skompilowana za pomocą VS2015, a więc zostanie ułożona jako blok struktur zawierających void *. Zakładając wyrównanie 32-bitowe, elementy te byłyby oddalone od siebie o 4 bajty. Więc _iob [0] jest na offset 0, _iob [1] na 4, a _iob [2] na offset 8. Zamiast tego kod wywołujący oczekuje, że PLIK będzie znacznie dłuższy, wyrównany do 32 bajtów w moim systemie, i tak weźmie adres zwróconej tablicy i doda 0 bajtów, aby dostać się do elementu zerowego (ten jest w porządku), ale dla _iob [1] wywnioskuje, że musi dodać 32 bajty, a dla _iob [2] wydedukuje że musi dodać 64 bajty (bo tak to wyglądało w nagłówkach VS2008). I rzeczywiście zdemontowany kod VS2008 to pokazuje.
Drugim problemem związanym z powyższym rozwiązaniem jest kopiowanie zawartości struktury FILE (* stdin), a nie uchwytu FILE *. Tak więc każdy kod VS2008 szukałby innej podstawowej struktury niż VS2015. Może to zadziałać, jeśli struktura zawiera tylko wskaźniki, ale to duże ryzyko. W każdym razie pierwsza kwestia czyni to nieistotnym.
Jedyny hack, jaki udało mi się wymyślić, to taki, w którym __iob_func chodzi po stosie wywołań, aby dowiedzieć się, którego rzeczywistego uchwytu pliku szuka (na podstawie przesunięcia dodanego do zwróconego adresu) i zwraca obliczoną wartość taką, że daje właściwą odpowiedź. To jest tak samo szalone, jak się wydaje, ale prototyp tylko dla x86 (nie x64) jest wymieniony poniżej dla twojej rozrywki. W moich eksperymentach zadziałało dobrze, ale przebieg może się różnić - niezalecany do użytku produkcyjnego!
#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;
}