Seperti yang dijawab di atas, jawaban yang benar adalah mengkompilasi semuanya dengan VS2015, tetapi untuk kepentingan berikut ini adalah analisis saya tentang masalah tersebut.
Simbol ini tampaknya tidak didefinisikan di pustaka statis mana pun yang disediakan oleh Microsoft sebagai bagian dari VS2015, yang agak aneh karena yang lainnya. Untuk mengetahui alasannya, kita perlu melihat deklarasi fungsi itu dan, yang lebih penting, bagaimana penggunaannya.
Berikut potongan dari header Visual Studio 2008:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Jadi kita dapat melihat bahwa tugas fungsinya adalah mengembalikan awal larik objek FILE (bukan menangani, "FILE *" adalah pegangannya, FILE adalah struktur data buram yang mendasari yang menyimpan barang negara penting). Pengguna fungsi ini adalah tiga makro stdin, stdout dan stderr yang digunakan untuk berbagai panggilan fscanf, gaya fprintf.
Sekarang mari kita lihat bagaimana Visual Studio 2015 mendefinisikan hal yang sama:
_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))
Jadi pendekatannya telah berubah untuk fungsi pengganti sekarang mengembalikan pegangan file daripada alamat dari array objek file, dan makro telah berubah untuk hanya memanggil fungsi yang meneruskan nomor pengenal.
Jadi mengapa mereka tidak / kami menyediakan API yang kompatibel? Ada dua aturan utama yang tidak dapat dilanggar Microsoft dalam hal implementasi aslinya melalui __iob_func:
- Harus ada larik dari tiga struktur FILE yang dapat diindeks dengan cara yang sama seperti sebelumnya.
- Tata letak struktural FILE tidak dapat diubah.
Setiap perubahan di salah satu hal di atas berarti kode terkompilasi yang ada yang ditautkan ke sana akan menjadi sangat salah jika API itu dipanggil.
Mari kita lihat bagaimana FILE dulu / didefinisikan.
Pertama definisi VS2008 FILE:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Dan sekarang definisi FILE VS2015:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
Jadi, inilah intinya: struktur telah berubah bentuk. Kode terkompilasi yang ada yang merujuk ke __iob_func bergantung pada fakta bahwa data yang dikembalikan adalah array yang dapat diindeks dan dalam array itu elemen-elemennya memiliki jarak yang sama.
Solusi yang mungkin disebutkan dalam jawaban di atas di sepanjang baris ini tidak akan berfungsi (jika dipanggil) karena beberapa alasan:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
Larik FILE _iob akan dikompilasi dengan VS2015 dan karenanya akan diletakkan sebagai blok struktur yang berisi void *. Dengan asumsi perataan 32-bit, elemen-elemen ini akan terpisah 4 byte. Jadi _iob [0] berada di offset 0, _iob [1] di offset 4 dan _iob [2] di offset 8. Kode pemanggil malah akan mengharapkan FILE lebih panjang, sejajar pada 32 byte di sistem saya, dan seterusnya itu akan mengambil alamat dari array yang dikembalikan dan menambahkan 0 byte untuk sampai ke elemen nol (yang itu tidak apa-apa), tetapi untuk _iob [1] itu akan menyimpulkan bahwa ia perlu menambahkan 32 byte dan untuk _iob [2] itu akan menyimpulkan bahwa ia perlu menambahkan 64-byte (karena itulah tampilannya di header VS2008). Dan memang kode yang dibongkar untuk VS2008 menunjukkan hal ini.
Masalah sekunder dengan solusi di atas adalah bahwa ia menyalin konten dari struktur FILE (* stdin), bukan pegangan FILE *. Jadi setiap kode VS2008 akan melihat struktur dasar yang berbeda dengan VS2015. Ini mungkin berhasil jika struktur hanya berisi petunjuk, tetapi itu risikonya besar. Bagaimanapun, masalah pertama membuat ini tidak relevan.
Satu-satunya peretasan yang bisa saya impikan adalah di mana __iob_func menjalankan tumpukan panggilan untuk mengetahui pegangan file aktual mana yang mereka cari (berdasarkan offset yang ditambahkan ke alamat yang dikembalikan) dan mengembalikan nilai yang dihitung sedemikian rupa sehingga itu memberikan jawaban yang benar. Ini sama gilanya kedengarannya, tetapi prototipe untuk x86 saja (bukan x64) tercantum di bawah ini untuk hiburan Anda. Ini berfungsi dengan baik dalam eksperimen saya, tetapi jarak tempuh Anda mungkin berbeda - tidak disarankan untuk penggunaan produksi!
#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;
}