
تحية طيبة
الرفاق العاكسون ، romhackers: في الأساس سوف تكرس هذه المقالة لك. في ذلك ، سوف أخبرك بكيفية كتابة البرنامج المساعد لمصحح أخطاء IDA Pro
الخاص بك. نعم ، كانت هناك بالفعل المحاولة الأولى لبدء القصة ، ولكن منذ ذلك الحين تدفقت كميات كبيرة من المياه ، تم تنقيح العديد من المبادئ. بشكل عام ، قادوا!
مقدمة غنائية
في الواقع ، من المقالات السابقة ( واحد ، اثنان ، ثلاثة ) ، أعتقد أنه لن يكون سرا أن معالجي المفضل هو Motorola 68000
. بالمناسبة ، تعمل سيدتي القديمة المفضلة Sega Mega Drive
/ Genesis
على ذلك. وبما أنني كنت مهتمًا دائمًا بكيفية ترتيب ألعاب Segov ، فمنذ الأشهر الأولى من استخدام الكمبيوتر ، قررت أن أعمق في غابة التفكيك والعكس لوقت طويل.
هذه هي الطريقة التي أتت بها أدوات Smd IDA .
يتضمن المشروع العديد من الأشياء المساعدة التي تجعل من عمل دراسة روم على Sega أسهل بكثير: لودر ، مصحح أخطاء ، مساعد لأوامر VDP
. كل شيء كتب لـ IDA 6.8
، وعمل بشكل جيد. لكن عندما قررت أن أخبر العالم كيف فعلت كل ذلك ، أصبح من الواضح أنه سيكون من الصعب جدًا عرض مثل هذا الرمز على الناس ، وحتى لوصفه أكثر من ذلك. لذلك ، لم أستطع القيام بذلك بعد ذلك.
ثم خرج IDA 7.0
. ظهرت الرغبة في نقل مشروعي إلى ذلك فورًا ، لكن بنية محاكي Gens
، التي قمت على أساسها كتبت مصحح الأخطاء ، تبين أنها غير مناسبة للنقل: إدراج x86
التجميع ، العكازات ، الكود الذي كان من الصعب فهمه ، وأكثر من ذلك بكثير. ولم تبدأ لعبة Pier Solar and the Great Architects
، التي تم إصدارها على الخراطيش في عام 2010 ، والتي أردت استكشافها حقًا (وهناك الكثير من الحيل المضادة للمضاهاة) ، في Gens
.

بحثًا عن مصدر محاكي مناسب يمكن تكييفه مع مصحح الأخطاء ، عثرت في النهاية على Genesis Plus GX من EkeEke
. لذلك ظهر هذا المقال.
الجزء الأول: المصحح الأساسية
يتعامل Musashi مع محاكاة إرشادات معالج Motorola في Genesis Plus GX
. مصدره الأصلي لديه بالفعل وظائف تصحيح الأخطاء الأساسية (خطاف لتنفيذ التعليمات) ، لكن EkeEke
قرر إزالته على أنه غير ضروري. نعود.


الآن الشيء الأكثر أهمية: تحتاج إلى اتخاذ قرار بشأن هندسة المصحح. المتطلبات هي كما يلي:
- فواصل (نقاط توقف) للتنفيذ ، للقراءة والكتابة إلى الذاكرة
- وظيفة
Step Into
Step Over
، Step Over
- وقفة ،
Resume
مضاهاة - قراءة / تعيين السجلات ، ذاكرة القراءة / الكتابة
إذا كانت هذه النقاط الأربع هي عمل مصحح الأخطاء من الداخل ، فلا يزال عليك التفكير في الوصول إلى هذه الوظيفة من الخارج. إضافة عنصر آخر:
- بروتوكول اتصال خادم المصحح (kernel) مع عميل مصحح الأخطاء (واجهة المستخدم الرسومية ، المستخدم)
المصحح الأساسية: قائمة كسر
لتنفيذ القائمة ، نبدأ الهيكل التالي:
typedef struct breakpoint_s { struct breakpoint_s *next, *prev; int enabled; int width; bpt_type_t type; unsigned int address; } breakpoint_t;
سيقوم الحقلان التاليان والسابقان بتخزين المؤشرات على العنصر التالي والسابق ، على التوالي.
سيتم تخزين الحقل الممكّن 0
إذا كانت نقطة التوقف هذه بحاجة إلى تخطي اختبارات التشغيل.
width
- عدد البايتات التي تبدأ من العنوان في حقل address
الذي تغطيه الكسارة.
حسنًا ، في حقل type
سنخزن نوع نقطة التوقف (التنفيذ والقراءة والكتابة). مزيد من التفاصيل أدناه.
للعمل مع قائمة نقاط التوقف ، أضفت الوظائف التالية:
وظائف نقطة توقف static breakpoint_t *first_bp = NULL; static breakpoint_t *add_bpt(bpt_type_t type, unsigned int address, int width) { breakpoint_t *bp = (breakpoint_t *)malloc(sizeof(breakpoint_t)); bp->type = type; bp->address = address; bp->width = width; bp->enabled = 1; if (first_bp) { bp->next = first_bp; bp->prev = first_bp->prev; first_bp->prev = bp; bp->prev->next = bp; } else { first_bp = bp; bp->next = bp; bp->prev = bp; } return bp; } static void delete_breakpoint(breakpoint_t * bp) { if (bp == first_bp) { if (bp->next == bp) { first_bp = NULL; } else { first_bp = bp->next; } } bp->next->prev = bp->prev; bp->prev->next = bp->next; free(bp); } static breakpoint_t *next_breakpoint(breakpoint_t *bp) { return bp->next != first_bp ? bp->next : 0; } static breakpoint_t *find_breakpoint(unsigned int address, bpt_type_t type) { breakpoint_t *p; for (p = first_bp; p; p = next_breakpoint(p)) { if ((p->address == address) && ((p->type == BPT_ANY) || (p->type & type))) return p; } return 0; } static void remove_bpt(unsigned int address, bpt_type_t type) { breakpoint_t *bpt; if ((bpt = find_breakpoint(address, type))) delete_breakpoint(bpt); } static int count_bpt_list() { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { ++i; } return i; } static void get_bpt_data(int index, bpt_data_t *data) { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { if (i == index) { data->address = p->address; data->width = p->width; data->type = p->type; data->enabled = p->enabled; break; } ++i; } } static void clear_bpt_list() { while (first_bp != NULL) delete_breakpoint(first_bp); } static void init_bpt_list() { if (first_bp) clear_bpt_list(); } void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value) { if (!dbg_req || !dbg_req->dbg_active || dbg_dont_check_bp) return; breakpoint_t *bp; for (bp = first_bp; bp; bp = next_breakpoint(bp)) { if (!(bp->type & type) || !bp->enabled) continue; if ((address <= (bp->address + bp->width)) && ((address + width) >= bp->address)) { dbg_req->dbg_paused = 1; break; } } }
المصحح الأساسية: المتغيرات الأساسية
في الواقع ، لقد تجسست هذا التطبيق في مصحح أخطاء PCSXR آخر.
أضف المتغيرات التي ستخزن حالة المحاكاة:
static int dbg_first_paused, dbg_trace, dbg_dont_check_bp; static int dbg_step_over; static int dbg_last_pc; static unsigned int dbg_step_over_addr; static int dbg_active, dbg_paused;
سيكون المتغير dbg_first_paused
مسؤولاً عن إيقاف المحاكاة في بداية تصحيح الأخطاء. إذا كانت القيمة 0
- فأنت بحاجة إلى إيقاف المحاكاة مؤقتًا وإرسال رسالة إلى العميل تفيد بأنه تم بدء المحاكاة. بعد الإيقاف المؤقت الأول ، اضبط الرقم 1
.
نحتاج إلى dbg_trace
للتنفيذ وفقًا لتعليمات واحدة (وظيفة Step Into
dbg_trace
). إذا كانت تساوي 1
، فإننا ننفذ تعليمة واحدة ، ونوقف مؤقتًا ، ونعيد ضبط القيمة إلى 0
.
لقد قمت بتعيين المتغير dbg_dont_check_bp
بحيث لا تعمل فواصل ذاكرة القراءة / الكتابة إذا كان المصحح قد قام بذلك.
سيتم تخزين dbg_step_over
في 1
إذا كنا في وضع " Step Over
حتى يصبح PC
الحالي ( عداد البرامج ، المعروف أيضًا باسم Instruction Pointer ) مساويًا للعنوان في dbg_step_over_addr
. بعد ذلك ، يتم إعادة تعيين كلا المتغيرات. dbg_step_over_addr
حساب قيمة dbg_step_over_addr
لاحقًا.
قمت بإعداد المتغير dbg_last_pc
لحالة واحدة محددة: عندما نقف بالفعل عند استراحة ، ويطلب العميل Resume
. حتى لا يعمل الكسارة مرة أخرى ، أقارن عنوان آخر PC
في هذا المتغير بالعنوان الجديد ، وإذا كانت القيم مختلفة ، يمكنك التحقق من نقطة التوقف على PC
الحالي.
dbg_active
- في الواقع ، تقوم بتخزين الحالة 1
عندما يكون التصحيح نشطًا وتحتاج إلى التحقق من الفواصل ومعالجة طلبات العميل.
مع متغير dbg_paused
، أعتقد أن كل شيء واضح: 1
- نحن في توقف مؤقت (على سبيل المثال ، بعد فترة انقطاع) ، ونحن في انتظار أوامر من العميل ، 0
- نحن نتبع التعليمات.
نكتب وظائف للعمل مع هذه المتغيرات:
static void pause_debugger() { dbg_trace = 1; dbg_paused = 1; } static void resume_debugger() { dbg_trace = 0; dbg_paused = 0; } static void detach_debugger() { clear_bpt_list(); resume_debugger(); } static void activate_debugger() { dbg_active = 1; } static void deactivate_debugger() { dbg_active = 0; }
نرى أنه في تنفيذ detach_debugger()
اعتدت على مسح قائمة الاستراحات. يعد ذلك ضروريًا حتى بعد قطع اتصال العميل ، لا تستمر نقاط التوقف القديمة في العمل.
المصحح الأساسية: ننفذ ربط على التعليمات
في الواقع ، هنا سيتم العمل الرئيسي مع توقف مؤقت ، مضاهاة مستمرة ، Step Into
، Step Over
.
فيما يلي رمز دالة process_breakpoints()
:
void process_breakpoints() { int handled_event = 0; int is_step_over = 0; int is_step_in = 0; if (!dbg_active) return; unsigned int pc = m68k_get_reg(M68K_REG_PC); if (dbg_paused && dbg_first_paused && !dbg_trace) longjmp(jmp_env, 1); if (!dbg_first_paused) { dbg_first_paused = 1; dbg_paused = 1;
دعنا نفهم:
- إذا لم يتم تمكين التصحيح ، فقم فقط بالخروج من الخطاف
longjmp
هناك حاجة إلى الخدعة باستخدام setjmp
/ longjmp
لأن RetroArch
shell RetroArch
، التي كتبنا RetroArch
الخاصة بها من Genesis Plus GX
، والتي نقوم بتشغيل المحاكاة بها ، معلقة بانتظار المحاكي للخروج من وظيفة عرض الإطار. سأعرض الجزء الثاني من الخدعة في وقت لاحق ، لأنه أنها تمس قذيفة على المحاكي ، بدلا من الأساسية.- إذا كانت هذه هي العملية الأولى للخطاف ، وبالتالي ، بداية المحاكاة ، فإننا نتوقف مؤقتًا ونرسل حدث بدء المحاكاة إلى العميل.
- إذا كان العميل قد أرسل أمر
Step Into
، فسنقوم dbg_trace
قيمة متغير dbg_trace
المحاكاة على الإيقاف المؤقت. نرسل الحدث المقابل إلى العميل. - إذا لم نتوقف مؤقتًا ، يتم تشغيل وضع
Step Over
، ويكون PC
الحالي مساويًا لعنوان الوجهة dbg_step_over_addr
، فنحن dbg_step_over_addr
المتغيرات والإيقاف المؤقت الضروريين. - نتحقق من نقطة الإيقاف هذه إذا لم نكن متواجدين عليها الآن ، وإذا نجحت الاستراحة ، فإننا نتوقف ونرسل للعميل حدثًا حول
Step Over
أو الاستراحة. - إذا لم يكن هذا انهيارًا ، وليس
Step Into
، وليس Step Over
، فعندئذ طلب العميل الاستراحة. نرسل حدثًا حول الإيقاف المؤقت الذي تم تشغيله. - نحن ننفذ الحيلة باستخدام
longjump
كتطبيق حلقة لا نهائية من انتظار الإجراءات من العميل أثناء توقف مؤقت.
لم يكن رمز حساب عنوان Step Over
بهذه البساطة التي قد تخمنها أولاً. يحتوي معالج Motorola على أطوال مختلفة من التعليمات ، لذلك يجب عليك مراعاة العنوان التالي يدويًا ، اعتمادًا على شفرة التشغيل. علاوة على ذلك ، تحتاج إلى تجنب تعليمات مثل bra
، jmp
، rts
القفزات الشرطية إلى الأمام ، وتنفيذها كـ Step Into
. التنفيذ على النحو التالي:
رمز الدالة Calc_step_over () static unsigned int calc_step_over() { unsigned int pc = m68k_get_reg(M68K_REG_PC); unsigned int sp = m68k_get_reg(M68K_REG_SP); unsigned int opc = m68ki_read_imm_16(); unsigned int dest_pc = (unsigned int)(-1);
مصحح أخطاء Kernel: تهيئة وإيقاف التصحيح
كل شيء بسيط هنا:
void stop_debugging() {
مصحح أخطاء Kernel: تطبيق بروتوكول
يمكن استدعاء بروتوكول الاتصال بين خادم التصحيح وعميل العميل بأمان القلب الثاني من عملية تصحيح الأخطاء لأن ينفذ وظيفة معالجة الطلبات من العميل ، وردود الفعل عليها.
تقرر تطبيق على أساس الذاكرة المشتركة ، لأنه من الضروري إرسال كتل كبيرة من الذاكرة: VRAM
، RAM
، ROM
، وعبر الشبكة سيكون هذا أكثر متعة.
الجوهر هو هذا: يقوم kernel بإنشاء ذاكرة مشتركة بهيكل محدد مسبقًا ، ويتوقع الطلبات الواردة من العميل. بعد معالجة الطلب ، يتم حفظ الإجابة في نفس الذاكرة ، وتتم إضافة المعلومات المقابلة إلى قائمة أحداث مصحح الأخطاء في نفس الذاكرة.
تم اختيار النموذج الأولي على النحو التالي:
قم بتنزيل حزمة المصدر debug_wrap.h #ifndef _DEBUG_WRAP_H_ #define _DEBUG_WRAP_H_ #ifdef __cplusplus extern "C" { #endif #include <Windows.h> #define SHARED_MEM_NAME "GX_PLUS_SHARED_MEM" #define MAX_BREAKPOINTS 1000 #define MAX_DBG_EVENTS 20 #ifndef MAXROMSIZE #define MAXROMSIZE ((unsigned int)0xA00000) #endif #pragma pack(push, 4) typedef enum { BPT_ANY = (0 << 0), // M68K BPT_M68K_E = (1 << 0), BPT_M68K_R = (1 << 1), BPT_M68K_W = (1 << 2), BPT_M68K_RW = BPT_M68K_R | BPT_M68K_W, // VDP BPT_VRAM_R = (1 << 3), BPT_VRAM_W = (1 << 4), BPT_VRAM_RW = BPT_VRAM_R | BPT_VRAM_W, BPT_CRAM_R = (1 << 5), BPT_CRAM_W = (1 << 6), BPT_CRAM_RW = BPT_CRAM_R | BPT_CRAM_W, BPT_VSRAM_R = (1 << 7), BPT_VSRAM_W = (1 << 8), BPT_VSRAM_RW = BPT_VSRAM_R | BPT_VSRAM_W, // Z80 BPT_Z80_E = (1 << 11), BPT_Z80_R = (1 << 12), BPT_Z80_W = (1 << 13), BPT_Z80_RW = BPT_Z80_R | BPT_Z80_W, // REGS BPT_VDP_REG = (1 << 9), BPT_M68K_REG = (1 << 10), } bpt_type_t; typedef enum { REQ_NO_REQUEST, REQ_GET_REGS, REQ_SET_REGS, REQ_GET_REG, REQ_SET_REG, REQ_READ_68K_ROM, REQ_READ_68K_RAM, REQ_WRITE_68K_ROM, REQ_WRITE_68K_RAM, REQ_READ_Z80, REQ_WRITE_Z80, REQ_ADD_BREAK, REQ_TOGGLE_BREAK, REQ_DEL_BREAK, REQ_CLEAR_BREAKS, REQ_LIST_BREAKS, REQ_ATTACH, REQ_PAUSE, REQ_RESUME, REQ_STOP, REQ_STEP_INTO, REQ_STEP_OVER, } request_type_t; typedef enum { REG_TYPE_M68K = (1 << 0), REG_TYPE_S80 = (1 << 1), REG_TYPE_Z80 = (1 << 2), REG_TYPE_VDP = (1 << 3), } register_type_t; typedef enum { DBG_EVT_NO_EVENT, DBG_EVT_STARTED, DBG_EVT_PAUSED, DBG_EVT_BREAK, DBG_EVT_STEP, DBG_EVT_STOPPED, } dbg_event_type_t; typedef struct { dbg_event_type_t type; unsigned int pc; char msg[256]; } debugger_event_t; typedef struct { int index; unsigned int val; } reg_val_t; typedef struct { unsigned int d0, d1, d2, d3, d4, d5, d6, d7; unsigned int a0, a1, a2, a3, a4, a5, a6, a7; unsigned int pc, sr, sp, usp, isp, ppc, ir; } regs_68k_data_t; typedef enum { REG_68K_D0, REG_68K_D1, REG_68K_D2, REG_68K_D3, REG_68K_D4, REG_68K_D5, REG_68K_D6, REG_68K_D7, REG_68K_A0, REG_68K_A1, REG_68K_A2, REG_68K_A3, REG_68K_A4, REG_68K_A5, REG_68K_A6, REG_68K_A7, REG_68K_PC, REG_68K_SR, REG_68K_SP, REG_68K_USP, REG_68K_ISP, REG_68K_PPC, REG_68K_IR, REG_VDP_00, REG_VDP_01, REG_VDP_02, REG_VDP_03, REG_VDP_04, REG_VDP_05, REG_VDP_06, REG_VDP_07, REG_VDP_08, REG_VDP_09, REG_VDP_0A, REG_VDP_0B, REG_VDP_0C, REG_VDP_0D, REG_VDP_0E, REG_VDP_0F, REG_VDP_10, REG_VDP_11, REG_VDP_12, REG_VDP_13, REG_VDP_14, REG_VDP_15, REG_VDP_16, REG_VDP_17, REG_VDP_18, REG_VDP_19, REG_VDP_1A, REG_VDP_1B, REG_VDP_1C, REG_VDP_1D, REG_VDP_1E, REG_VDP_1F, REG_VDP_DMA_LEN, REG_VDP_DMA_SRC, REG_VDP_DMA_DST, REG_Z80_PC, REG_Z80_SP, REG_Z80_AF, REG_Z80_BC, REG_Z80_DE, REG_Z80_HL, REG_Z80_IX, REG_Z80_IY, REG_Z80_WZ, REG_Z80_AF2, REG_Z80_BC2, REG_Z80_DE2, REG_Z80_HL2, REG_Z80_R, REG_Z80_R2, REG_Z80_IFFI1, REG_Z80_IFFI2, REG_Z80_HALT, REG_Z80_IM, REG_Z80_I, } regs_all_t; typedef struct { unsigned int pc, sp, af, bc, de, hl, ix, iy, wz; unsigned int af2,bc2,de2,hl2; unsigned char r, r2, iff1, iff2, halt, im, i; } regs_z80_data_t; typedef struct { unsigned char regs_vdp[0x20]; unsigned short dma_len; unsigned int dma_src, dma_dst; } vdp_regs_t; typedef struct { int type; // register_type_t regs_68k_data_t regs_68k; reg_val_t any_reg; vdp_regs_t vdp_regs; regs_z80_data_t regs_z80; } register_data_t; typedef struct { int size; unsigned int address; unsigned char m68k_rom[MAXROMSIZE]; unsigned char m68k_ram[0x10000]; unsigned char z80_ram[0x2000]; } memory_data_t; typedef struct { bpt_type_t type; unsigned int address; int width; int enabled; } bpt_data_t; typedef struct { int count; bpt_data_t breaks[MAX_BREAKPOINTS]; } bpt_list_t; typedef struct { request_type_t req_type; register_data_t regs_data; memory_data_t mem_data; bpt_data_t bpt_data; int dbg_events_count; debugger_event_t dbg_events[MAX_DBG_EVENTS]; bpt_list_t bpt_list; int dbg_active, dbg_paused; int is_ida; } dbg_request_t; #pragma pack(pop) dbg_request_t *open_shared_mem(); void close_shared_mem(dbg_request_t **request); int recv_dbg_event(dbg_request_t *request, int wait); void send_dbg_request(dbg_request_t *request, request_type_t type); #ifdef __cplusplus } #endif #endif
الحقل الأول في الهيكل سيكون لدينا نوع الطلب:
- قراءة / مجموعة السجلات
- قراءة / كتابة الذاكرة
- العمل مع نقاط التوقف
- إيقاف مؤقت / متابعة مضاهاة ، قطع / إيقاف مصحح الأخطاء
Step Into
/ Step Over
فيما يلي سجلات M68K
، Z80
، VDP
. فيما يلي كتل الذاكرة ROM
و RAM
و VRAM
و Z80
.
لإضافة / إزالة الكراك ، قمت أيضًا بإنشاء الهيكل المقابل. حسنًا ، توجد قائمتهم هنا أيضًا (بالنسبة للجزء الأكبر ، فهي معروضة فقط في واجهة المستخدم الرسومية ، دون الحاجة إلى تذكر جميع الفواصل المثبتة ، كما تفعل IDA
).
فيما يلي قائمة بأحداث التصحيح:
- بدأ التصحيح (مطلوب لـ
IDA Pro
) - تم إيقاف التصحيح مؤقتًا (يتم حفظ
PC
الذي تم إيقاف المحاكاة عليه مؤقتًا في الوقت الحالي) - تم عمل Breakpoint (أيضًا بتخزين قيمة
PC
الذي حدثت عليه العملية) - تم تنفيذ
Step Into
أو Step Over
(أيضًا ، في الواقع ، لا يلزم إلا IDA
، لأنه يمكنك القيام بحدث وقفة واحدة فقط) - تم إيقاف عملية المحاكاة. بعد النقر فوق الزر "
Stop
في IDA
دون تلقي هذا الحدث ، سينتظر ما لا نهاية للتوقف
مسلحين بفكرة البروتوكول ، ننفذ معالجة طلبات العميل ، وبالتالي نحصل على كود kernel لمصحح الأخطاء التالي:
قم بتنزيل حزمة المصدر debug.c #include "debug.h" #include "shared.h" #define m68ki_cpu m68k #define MUL (7) #ifndef BUILD_TABLES #include "m68ki_cycles.h" #endif #include "m68kconf.h" #include "m68kcpu.h" #include "m68kops.h" #include "vdp_ctrl.h" #include "Z80.h" static int dbg_first_paused, dbg_trace, dbg_dont_check_bp; static int dbg_step_over; static int dbg_last_pc; static unsigned int dbg_step_over_addr; static dbg_request_t *dbg_req = NULL; static HANDLE hMapFile = 0; typedef struct breakpoint_s { struct breakpoint_s *next, *prev; int enabled; int width; bpt_type_t type; unsigned int address; } breakpoint_t; static breakpoint_t *first_bp = NULL; static breakpoint_t *add_bpt(bpt_type_t type, unsigned int address, int width) { breakpoint_t *bp = (breakpoint_t *)malloc(sizeof(breakpoint_t)); bp->type = type; bp->address = address; bp->width = width; bp->enabled = 1; if (first_bp) { bp->next = first_bp; bp->prev = first_bp->prev; first_bp->prev = bp; bp->prev->next = bp; } else { first_bp = bp; bp->next = bp; bp->prev = bp; } return bp; } static void delete_breakpoint(breakpoint_t * bp) { if (bp == first_bp) { if (bp->next == bp) { first_bp = NULL; } else { first_bp = bp->next; } } bp->next->prev = bp->prev; bp->prev->next = bp->next; free(bp); } static breakpoint_t *next_breakpoint(breakpoint_t *bp) { return bp->next != first_bp ? bp->next : 0; } static breakpoint_t *find_breakpoint(unsigned int address, bpt_type_t type) { breakpoint_t *p; for (p = first_bp; p; p = next_breakpoint(p)) { if ((p->address == address) && ((p->type == BPT_ANY) || (p->type & type))) return p; } return 0; } static void remove_bpt(unsigned int address, bpt_type_t type) { breakpoint_t *bpt; if ((bpt = find_breakpoint(address, type))) delete_breakpoint(bpt); } static int count_bpt_list() { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { ++i; } return i; } static void get_bpt_data(int index, bpt_data_t *data) { breakpoint_t *p; int i = 0; for (p = first_bp; p; p = next_breakpoint(p)) { if (i == index) { data->address = p->address; data->width = p->width; data->type = p->type; data->enabled = p->enabled; break; } ++i; } } static void clear_bpt_list() { while (first_bp != NULL) delete_breakpoint(first_bp); } static void init_bpt_list() { if (first_bp) clear_bpt_list(); } void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value) { if (!dbg_req || !dbg_req->dbg_active || dbg_dont_check_bp) return; breakpoint_t *bp; for (bp = first_bp; bp; bp = next_breakpoint(bp)) { if (!(bp->type & type) || !bp->enabled) continue; if ((address <= (bp->address + bp->width)) && ((address + width) >= bp->address)) { dbg_req->dbg_paused = 1; break; } } } static void pause_debugger() { dbg_trace = 1; dbg_req->dbg_paused = 1; } static void resume_debugger() { dbg_trace = 0; dbg_req->dbg_paused = 0; } static void detach_debugger() { clear_bpt_list(); resume_debugger(); } static void activate_debugger() { dbg_req->dbg_active = 1; } static void deactivate_debugger() { dbg_req->dbg_active = 0; } int activate_shared_mem() { hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(dbg_request_t), SHARED_MEM_NAME); if (hMapFile == 0) { return -1; } dbg_req = (dbg_request_t*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(dbg_request_t)); if (dbg_req == 0) { CloseHandle(hMapFile); return -1; } memset(dbg_req, 0, sizeof(dbg_request_t)); return 0; } void deactivate_shared_mem() { UnmapViewOfFile(dbg_req); CloseHandle(hMapFile); hMapFile = NULL; dbg_req = NULL; } static unsigned int calc_step_over() { unsigned int pc = m68k_get_reg(M68K_REG_PC); unsigned int sp = m68k_get_reg(M68K_REG_SP); unsigned int opc = m68ki_read_imm_16(); unsigned int dest_pc = (unsigned int)(-1); // jsr if ((opc & 0xFFF8) == 0x4E90) { m68k_op_jsr_32_ai(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFF8) == 0x4EA8) { m68k_op_jsr_32_di(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFF8) == 0x4EB0) { m68k_op_jsr_32_ix(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EB8) { m68k_op_jsr_32_aw(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EB9) { m68k_op_jsr_32_al(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EBA) { m68k_op_jsr_32_pcdi(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x4EBB) { m68k_op_jsr_32_pcix(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } // bsr else if ((opc & 0xFFFF) == 0x6100) { m68k_op_bsr_16(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFFFF) == 0x61FF) { m68k_op_bsr_32(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } else if ((opc & 0xFF00) == 0x6100) { m68k_op_bsr_8(); m68k_op_rts_32(); dest_pc = m68k_get_reg(M68K_REG_PC); } // dbf else if ((opc & 0xfff8) == 0x51C8) { dest_pc = m68k_get_reg(M68K_REG_PC) + 2; } m68k_set_reg(M68K_REG_PC, pc); m68k_set_reg(M68K_REG_SP, sp); return dest_pc; } void process_request() { if (!dbg_req || !dbg_req->dbg_active) return; if (dbg_req->req_type == REQ_NO_REQUEST) return; switch (dbg_req->req_type) { case REQ_GET_REG: { register_data_t *regs_data = &dbg_req->regs_data; if (regs_data->type & REG_TYPE_M68K) regs_data->any_reg.val = m68k_get_reg(regs_data->any_reg.index); if (regs_data->type & REG_TYPE_VDP) regs_data->any_reg.val = reg[regs_data->any_reg.index]; if (regs_data->type & REG_TYPE_Z80) { if (regs_data->any_reg.index >= 0 && regs_data->any_reg.index <= 12) // PC <-> HL2 { regs_data->any_reg.val = ((unsigned int *)&Z80.pc)[regs_data->any_reg.index]; } else if (regs_data->any_reg.index >= 13 && regs_data->any_reg.index <= 19) // R <-> I { regs_data->any_reg.val = ((unsigned char *)&Z80.r)[regs_data->any_reg.index - 13]; } } } break; case REQ_SET_REG: { register_data_t *regs_data = &dbg_req->regs_data; if (regs_data->type & REG_TYPE_M68K) m68k_set_reg(regs_data->any_reg.index, regs_data->any_reg.val); if (regs_data->type & REG_TYPE_VDP) reg[regs_data->any_reg.index] = regs_data->any_reg.val; if (regs_data->type & REG_TYPE_Z80) { if (regs_data->any_reg.index >= 0 && regs_data->any_reg.index <= 12) // PC <-> HL2 { ((unsigned int *)&Z80.pc)[regs_data->any_reg.index] = regs_data->any_reg.val; } else if (regs_data->any_reg.index >= 13 && regs_data->any_reg.index <= 19) // R <-> I { ((unsigned char *)&Z80.r)[regs_data->any_reg.index - 13] = regs_data->any_reg.val & 0xFF; } } } break; case REQ_GET_REGS: case REQ_SET_REGS: { register_data_t *regs_data = &dbg_req->regs_data; if (regs_data->type & REG_TYPE_M68K) { regs_68k_data_t *m68kr = ®s_data->regs_68k; if (dbg_req->req_type == REQ_GET_REGS) { m68kr->d0 = m68k_get_reg(M68K_REG_D0); m68kr->d1 = m68k_get_reg(M68K_REG_D1); m68kr->d2 = m68k_get_reg(M68K_REG_D2); m68kr->d3 = m68k_get_reg(M68K_REG_D3); m68kr->d4 = m68k_get_reg(M68K_REG_D4); m68kr->d5 = m68k_get_reg(M68K_REG_D5); m68kr->d6 = m68k_get_reg(M68K_REG_D6); m68kr->d7 = m68k_get_reg(M68K_REG_D7); m68kr->a0 = m68k_get_reg(M68K_REG_A0); m68kr->a1 = m68k_get_reg(M68K_REG_A1); m68kr->a2 = m68k_get_reg(M68K_REG_A2); m68kr->a3 = m68k_get_reg(M68K_REG_A3); m68kr->a4 = m68k_get_reg(M68K_REG_A4); m68kr->a5 = m68k_get_reg(M68K_REG_A5); m68kr->a6 = m68k_get_reg(M68K_REG_A6); m68kr->a7 = m68k_get_reg(M68K_REG_A7); m68kr->pc = m68k_get_reg(M68K_REG_PC); m68kr->sr = m68k_get_reg(M68K_REG_SR); m68kr->sp = m68k_get_reg(M68K_REG_SP); m68kr->usp = m68k_get_reg(M68K_REG_USP); m68kr->isp = m68k_get_reg(M68K_REG_ISP); m68kr->ppc = m68k_get_reg(M68K_REG_PPC); m68kr->ir = m68k_get_reg(M68K_REG_IR); } else { m68k_set_reg(M68K_REG_D0, m68kr->d0); m68k_set_reg(M68K_REG_D1, m68kr->d1); m68k_set_reg(M68K_REG_D2, m68kr->d2); m68k_set_reg(M68K_REG_D3, m68kr->d3); m68k_set_reg(M68K_REG_D4, m68kr->d4); m68k_set_reg(M68K_REG_D5, m68kr->d5); m68k_set_reg(M68K_REG_D6, m68kr->d6); m68k_set_reg(M68K_REG_D7, m68kr->d7); m68k_set_reg(M68K_REG_A0, m68kr->a0); m68k_set_reg(M68K_REG_A1, m68kr->a1); m68k_set_reg(M68K_REG_A2, m68kr->a2); m68k_set_reg(M68K_REG_A3, m68kr->a3); m68k_set_reg(M68K_REG_A4, m68kr->a4); m68k_set_reg(M68K_REG_A5, m68kr->a5); m68k_set_reg(M68K_REG_A6, m68kr->a6); m68k_set_reg(M68K_REG_A7, m68kr->a7); m68k_set_reg(M68K_REG_PC, m68kr->pc); m68k_set_reg(M68K_REG_SR, m68kr->sr); m68k_set_reg(M68K_REG_SP, m68kr->sp); m68k_set_reg(M68K_REG_USP, m68kr->usp); m68k_set_reg(M68K_REG_ISP, m68kr->isp); } } if (regs_data->type & REG_TYPE_VDP) { vdp_regs_t *vdp_regs = ®s_data->vdp_regs; for (int i = 0; i < (sizeof(vdp_regs) / sizeof(vdp_regs->regs_vdp[0])); ++i) { if (dbg_req->req_type == REQ_GET_REGS) vdp_regs->regs_vdp[i] = reg[i]; else reg[i] = vdp_regs->regs_vdp[i]; } if (dbg_req->req_type == REQ_GET_REGS) { vdp_regs->dma_len = (reg[20] << 8) | reg[19]; if (!vdp_regs->dma_len) vdp_regs->dma_len = 0x10000; vdp_regs->dma_src = vdp_dma_calc_src(); vdp_regs->dma_dst = vdp_dma_get_dst(); } } if (regs_data->type & REG_TYPE_Z80) { regs_z80_data_t *z80r = ®s_data->regs_z80; if (dbg_req->req_type == REQ_GET_REGS) { z80r->pc = Z80.pc.d; z80r->sp = Z80.sp.d; z80r->af = Z80.af.d; z80r->bc = Z80.bc.d; z80r->de = Z80.de.d; z80r->hl = Z80.hl.d; z80r->ix = Z80.ix.d; z80r->iy = Z80.iy.d; z80r->wz = Z80.wz.d; z80r->af2 = Z80.af2.d; z80r->bc2 = Z80.bc2.d; z80r->de2 = Z80.de2.d; z80r->hl2 = Z80.hl2.d; z80r->r = Z80.r; z80r->r2 = Z80.r2; z80r->iff1 = Z80.iff1; z80r->iff2 = Z80.iff2; z80r->halt = Z80.halt; z80r->im = Z80.im; z80r->i = Z80.i; } else { Z80.pc.d = z80r->pc; Z80.sp.d = z80r->sp; Z80.af.d = z80r->af; Z80.bc.d = z80r->bc; Z80.de.d = z80r->de; Z80.hl.d = z80r->hl; Z80.ix.d = z80r->ix; Z80.iy.d = z80r->iy; Z80.wz.d = z80r->wz; Z80.af2.d = z80r->af2; Z80.bc2.d = z80r->bc2; Z80.de2.d = z80r->de2; Z80.hl2.d = z80r->hl2; Z80.r = z80r->r; Z80.r2 = z80r->r2; Z80.iff1 = z80r->iff1; Z80.iff2 = z80r->iff2; Z80.halt = z80r->halt; Z80.im = z80r->im; Z80.i = z80r->i; } } } break; case REQ_READ_68K_ROM: case REQ_READ_68K_RAM: case REQ_READ_Z80: { dbg_dont_check_bp = 1; memory_data_t *mem_data = &dbg_req->mem_data; for (int i = 0; i < mem_data->size; ++i) { switch (dbg_req->req_type) { case REQ_READ_68K_ROM: mem_data->m68k_rom[mem_data->address + i] = m68ki_read_8(mem_data->address + i); break; case REQ_READ_68K_RAM: mem_data->m68k_ram[(mem_data->address + i) & 0xFFFF] = m68ki_read_8(mem_data->address + i); break; case REQ_READ_Z80: mem_data->z80_ram[(mem_data->address + i) & 0x1FFF] = z80_readmem(mem_data->address + i); break; default: break; } } dbg_dont_check_bp = 0; } break; case REQ_WRITE_68K_ROM: case REQ_WRITE_68K_RAM: case REQ_WRITE_Z80: { dbg_dont_check_bp = 1; memory_data_t *mem_data = &dbg_req->mem_data; for (int i = 0; i < mem_data->size; ++i) { switch (dbg_req->req_type) { case REQ_WRITE_68K_ROM: m68ki_write_8(mem_data->address + i, mem_data->m68k_rom[mem_data->address + i]); break; case REQ_WRITE_68K_RAM: m68ki_write_8(0xFF0000 | ((mem_data->address + i) & 0xFFFF), mem_data->m68k_ram[(mem_data->address + i) & 0xFFFF]); break; case REQ_WRITE_Z80: z80_writemem(mem_data->address + i, mem_data->z80_ram[(mem_data->address + i) & 0x1FFF]); break; default: break; } } dbg_dont_check_bp = 0; } break; case REQ_ADD_BREAK: { bpt_data_t *bpt_data = &dbg_req->bpt_data; if (!find_breakpoint(bpt_data->address, bpt_data->type)) add_bpt(bpt_data->type, bpt_data->address, bpt_data->width); } break; case REQ_TOGGLE_BREAK: { bpt_data_t *bpt_data = &dbg_req->bpt_data; breakpoint_t *bp = find_breakpoint(bpt_data->address, bpt_data->type); if (bp != NULL) bp->enabled = !bp->enabled; } break; case REQ_DEL_BREAK: { bpt_data_t *bpt_data = &dbg_req->bpt_data; remove_bpt(bpt_data->address, bpt_data->type); } break; case REQ_CLEAR_BREAKS: clear_bpt_list(); case REQ_LIST_BREAKS: { bpt_list_t *bpt_list = &dbg_req->bpt_list; bpt_list->count = count_bpt_list(); for (int i = 0; i < bpt_list->count; ++i) get_bpt_data(i, &bpt_list->breaks[i]); } break; case REQ_ATTACH: activate_debugger(); dbg_first_paused = 0; break; case REQ_PAUSE: pause_debugger(); break; case REQ_RESUME: resume_debugger(); break; case REQ_STOP: stop_debugging(); break; case REQ_STEP_INTO: { if (dbg_req->dbg_paused) { dbg_trace = 1; dbg_req->dbg_paused = 0; } } break; case REQ_STEP_OVER: { if (dbg_req->dbg_paused) { unsigned int dest_pc = calc_step_over(); if (dest_pc != (unsigned int)(-1)) { dbg_step_over = 1; dbg_step_over_addr = dest_pc; } else { dbg_step_over = 0; dbg_step_over_addr = 0; dbg_trace = 1; } dbg_req->dbg_paused = 0; } } break; default: break; } dbg_req->req_type = REQ_NO_REQUEST; } void send_dbg_event(dbg_event_type_t type) { dbg_req->dbg_events[dbg_req->dbg_events_count].type = type; dbg_req->dbg_events_count += 1; } void stop_debugging() { send_dbg_event(DBG_EVT_STOPPED); detach_debugger(); deactivate_debugger(); dbg_first_paused = dbg_req->dbg_paused = dbg_trace = dbg_dont_check_bp = dbg_step_over = dbg_step_over_addr = dbg_last_pc = 0; } void start_debugging() { if (dbg_req != NULL && dbg_req->dbg_active) return; activate_debugger(); init_bpt_list(); dbg_first_paused = dbg_req->dbg_paused = dbg_trace = dbg_dont_check_bp = dbg_step_over = dbg_step_over_addr = dbg_last_pc = 0; } int is_debugger_accessible() { return (dbg_req != NULL); } void process_breakpoints() { int handled_event = 0; int is_step_over = 0; int is_step_in = 0; unsigned int pc = m68k_get_reg(M68K_REG_PC); if (!dbg_req || !dbg_req->dbg_active) return; if (dbg_req->dbg_paused && dbg_first_paused && !dbg_trace) longjmp(jmp_env, 1); if (!dbg_first_paused) { dbg_first_paused = 1; dbg_req->dbg_paused = 1; dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; strncpy(dbg_req->dbg_events[dbg_req->dbg_events_count].msg, "gpgx", sizeof(dbg_req->dbg_events[dbg_req->dbg_events_count].msg)); send_dbg_event(DBG_EVT_STARTED); } if (dbg_trace) { is_step_in = 1; dbg_trace = 0; dbg_req->dbg_paused = 1; dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; send_dbg_event(DBG_EVT_STEP); handled_event = 1; } if (!dbg_req->dbg_paused) { if (dbg_step_over && pc == dbg_step_over_addr) { is_step_over = 1; dbg_step_over = 0; dbg_step_over_addr = 0; dbg_req->dbg_paused = 1; } if (dbg_last_pc != pc) check_breakpoint(BPT_M68K_E, 1, pc, pc); if (dbg_req->dbg_paused) { dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; send_dbg_event(is_step_over ? DBG_EVT_STEP : DBG_EVT_BREAK); handled_event = 1; } } if (dbg_first_paused && (!handled_event) && dbg_req->dbg_paused) { dbg_req->dbg_events[dbg_req->dbg_events_count].pc = pc; send_dbg_event(DBG_EVT_PAUSED); } dbg_last_pc = pc; if (dbg_req->dbg_paused && (!is_step_in || is_step_over)) { longjmp(jmp_env, 1); } } int is_debugger_paused() { return is_debugger_accessible() && dbg_req->dbg_paused && dbg_first_paused && !dbg_trace; }
debug.h #ifndef _DEBUG_H_ #define _DEBUG_H_ #ifdef __cplusplus extern "C" { #endif #include <setjmp.h> #include "debug_wrap.h" extern void start_debugging(); extern void stop_debugging(); extern int is_debugger_accessible(); extern void process_request(); extern int is_debugger_paused(); extern int activate_shared_mem(); extern void deactivate_shared_mem(); void check_breakpoint(bpt_type_t type, int width, unsigned int address, unsigned int value); extern jmp_buf jmp_env; #ifdef __cplusplus } #endif #endif
.
, check_breakpoint
VDP
#ifdef LOGVDP
. vdp_ctrl.c
:
check_breakpoint(BPT_VRAM_W, 2, addr, data); ... check_breakpoint(BPT_CRAM_W, 2, addr, data); ... check_breakpoint(BPT_VSRAM_W, 2, addr, data); ... check_breakpoint(BPT_VRAM_R, 2, addr, data); ... check_breakpoint(BPT_CRAM_R, 2, addr, data); ... check_breakpoint(BPT_VSRAM_R, 2, addr, data);
RAM
( m68kcpu.h
):
, , .
debug_wrap.c #include <Windows.h> #include <process.h> #include "debug_wrap.h" static HANDLE hMapFile = NULL, hStartFunc = NULL; dbg_request_t *open_shared_mem() { hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEM_NAME); if (hMapFile == NULL) { return NULL; } dbg_request_t *request = (dbg_request_t *)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(dbg_request_t)); if (request == NULL) { CloseHandle(hMapFile); return NULL; } return request; } void close_shared_mem(dbg_request_t **request) { UnmapViewOfFile(*request); CloseHandle(hMapFile); hMapFile = NULL; *request = NULL; } int recv_dbg_event(dbg_request_t *request, int wait) { while (request->dbg_active || request->dbg_events_count) { for (int i = 0; i < MAX_DBG_EVENTS; ++i) { if (request->dbg_events[i].type != DBG_EVT_NO_EVENT) { request->dbg_events_count -= 1; return i; } } if (!wait) return -1; Sleep(10); } return -1; } void send_dbg_request(dbg_request_t *request, request_type_t type) { if (!request) return; request->req_type = type; while (request->dbg_active && request->req_type != REQ_NO_REQUEST) { Sleep(10); } }
. , . , , , .
:
Genesis Plus GX
:
var.key = "genesis_plus_gx_debugger"; environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var); { if (!var.value || !strcmp(var.value, "disabled")) { if (is_debugger_accessible()) { stop_debugging(); stop_gui(); deactivate_shared_mem(); } } else { activate_shared_mem(); start_debugging(); run_gui(); } } ... { "genesis_plus_gx_debugger", "Debugger; disabled|enabled" },
RetroArch
:
, retro_run()
. ( ), . , retro_run()
, RetroArch
. setjmp()
/ longjmp()
. , retro_run()
:
if (is_debugger_paused()) { longjmp(jmp_env, 1); } int is_paused = setjmp(jmp_env); if (is_paused) { process_request(); return; }
retro_run()
process_request()
, , .
PS

Update :
- IDA Pro
, .