Mono源碼閱讀-崩潰機(jī)制

Mono源碼閱讀-崩潰機(jī)制

# 簡介

本文主要針對mono源碼中關(guān)于崩潰信號量處理的相關(guān)源碼進(jìn)行閱讀和研究锉走,源碼涉及的代碼文件如下:

  • mini.c

  • mini-posix.c

  • mini-exceptions.c

  • exceptions-arm.c

Install Signal Handler

add_signal_handler

所有信號handler的注冊函數(shù)都是調(diào)用 add_signal_handler的.

mono代碼里一共調(diào)用這個(gè)函數(shù)來注冊信號量的函數(shù)有:

interp.c

  • mono_runtime_install_handlers

mini-posix.c

  • mono_runtime_posix_install_handlers
  • mono_runtime_setup_stat_profiler (SIGPROF)

mono_runtime_posix_install_handlers

這里主要關(guān)注mini目錄下的信號量注冊.

Mono捕捉的信號:

  • SIGINT (if handle sigint)
  • SIGFPE
  • SIGQUIT
  • SIGILL
  • SIGBUS
  • SIGUSR2(if mono_jit_trace_calls != null)
  • SIGUSR1 -> mono_thread_get_abort_signal(0
  • SIGABRT
  • SIGSEGV
常量 解釋
SIGSEGV 非法內(nèi)存訪問(段錯(cuò)誤),試圖訪問未分配給自己的內(nèi)存, 或試圖往沒有寫權(quán)限的內(nèi)存地址寫數(shù)據(jù).
SIGINT 外部中斷糙俗,通常為用戶所發(fā)動(dòng), 程序終止(interrupt)信號, 在用戶鍵入INTR字符(通常是Ctrl-C)時(shí)發(fā)出赋咽,用于通知前臺進(jìn)程組終止進(jìn)程芦劣。
SIGILL 非法程序映像,例如非法指令, 執(zhí)行了非法指令. 通常是因?yàn)榭蓤?zhí)行文件本身出現(xiàn)錯(cuò)誤, 或者試圖執(zhí)行數(shù)據(jù)段. 堆棧溢出時(shí)也有可能產(chǎn)生這個(gè)信號泵殴。
SIGABRT 異常終止條件棚赔,例如 abort() 所起始的
SIGFPE 在發(fā)生致命的算術(shù)運(yùn)算錯(cuò)誤時(shí)發(fā)出. 不僅包括浮點(diǎn)運(yùn)算錯(cuò)誤, 還包括溢出及除數(shù)為0等其它所有的算術(shù)的錯(cuò)誤。
SIGQUIT 和SIGINT類似, 但由QUIT字符(通常是Ctrl-\)來控制. 進(jìn)程在因收到SIGQUIT退出時(shí)會產(chǎn)生core文件, 在這個(gè)意義上類似于一個(gè)程序錯(cuò)誤信號系吩。
SIGBUS 非法地址, 包括內(nèi)存地址對齊(alignment)出錯(cuò)来庭。比如訪問一個(gè)四個(gè)字長的整數(shù), 但其地址不是4的倍數(shù)。它與SIGSEGV的區(qū)別在于后者是由于對合法存儲地址的非法訪問觸發(fā)的(如訪問不屬于自己存儲空間或只讀存儲空間)穿挨。
SIGUSR1 留給用戶使用
SIGUSR1 留給用戶使用

注冊信號量的代碼:

void
mono_runtime_posix_install_handlers (void)
{


    sigset_t signal_set;


    if (mini_get_debug_options ()->handle_sigint)
        add_signal_handler (SIGINT, mono_sigint_signal_handler);


    add_signal_handler (SIGFPE, mono_sigfpe_signal_handler);
    add_signal_handler (SIGQUIT, sigquit_signal_handler);
    add_signal_handler (SIGILL, mono_sigill_signal_handler);
    add_signal_handler (SIGBUS, mono_sigsegv_signal_handler);
    if (mono_jit_trace_calls != NULL)
        add_signal_handler (SIGUSR2, sigusr2_signal_handler);


    add_signal_handler (mono_thread_get_abort_signal (), sigusr1_signal_handler);
    /* it seems to have become a common bug for some programs that run as parents
     * of many processes to block signal delivery for real time signals.
     * We try to detect and work around their breakage here.
     */
    sigemptyset (&signal_set);
    sigaddset (&signal_set, mono_thread_get_abort_signal ());
    sigprocmask (SIG_UNBLOCK, &signal_set, NULL);


    signal (SIGPIPE, SIG_IGN);


#ifndef MONO_CROSS_COMPILE
    add_signal_handler (SIGABRT, sigabrt_signal_handler);


    /* catch SIGSEGV */
    add_signal_handler (SIGSEGV, mono_sigsegv_signal_handler);
#endif
}

Signal Handler

所有的信號handler都是使用 SIG_HANDLER_SIGNATURE 宏來定義的:

mini-posix.c

  • sigabrt_signal_handler
  • sigprof_signal_handler
  • sigquit_signal_handler
  • siguser1_signal_handler
  • sigusr2_signal_handler

mini.c

  • mono_sigfpe_signal_handler
  • mono_sigill_signal_handler
  • mono_sigsegv_signal_handler
  • mono_sigint_signal_handler

SIGINT

void
SIG_HANDLER_SIGNATURE (mono_sigint_signal_handler)
{
    MonoException *exc;
    GET_CONTEXT;


    exc = mono_get_exception_execution_engine ("Interrupted (SIGINT).");

    mono_arch_handle_exception (ctx, exc, FALSE);
}

mono_arch_handle_exception

/*
* This is the function called from the signal handler
*/
gboolean
mono_arch_handle_exception (void *ctx, gpointer obj, gboolean test_only)
{
    MonoContext mctx;
    gboolean result;


    mono_arch_sigctx_to_monoctx (ctx, &mctx);


    result = mono_handle_exception (&mctx, obj, (gpointer)mctx.eip, test_only);
    /* restore the context so that returning from the signal handler will invoke
     * the catch clause
     */
    mono_arch_monoctx_to_sigctx (&mctx, ctx);
    return result;
}

SEGV

如果沒有 mono_domain_get() 或者沒有 jit_tls 則可以認(rèn)為該線程非管理線程, 則調(diào)用
mono_chain_signal 來調(diào)用注冊的chian signal handler 去處理, 如果該handler返回true, 則mono直接return不做任何處理 , 否則mono會調(diào)用
如果是管理線程, 那么和在C#里面Throw Exception的邏輯一樣, 調(diào)用mono_handle_exception去處理C#的異常.
mono_handle_native_sigsegv來打印堆棧并最后調(diào)用 abort()

這里的chain_signal_handler就是mono在注冊信號量的時(shí)候預(yù)先保存了之前的signal_handler
saved_handler

void
SIG_HANDLER_SIGNATURE (mono_sigsegv_signal_handler)
{
    MonoJitInfo *ji;
    MonoJitTlsData *jit_tls = TlsGetValue (mono_jit_tls_id);
    gpointer ip;


    GET_CONTEXT;


#if defined(MONO_ARCH_SOFT_DEBUG_SUPPORTED) && defined(HAVE_SIG_INFO)
    if (mono_arch_is_single_step_event (info, ctx)) {
        mono_debugger_agent_single_step_event (ctx);
        return;
    } else if (mono_arch_is_breakpoint_event (info, ctx)) {
        mono_debugger_agent_breakpoint_hit (ctx);
        return;
    }
#endif


#if !defined(PLATFORM_WIN32) && defined(HAVE_SIG_INFO)
    if (mono_aot_is_pagefault (info->si_addr)) {
        mono_aot_handle_pagefault (info->si_addr);
        return;
    }
#endif


    /* The thread might no be registered with the runtime */
    if (!mono_domain_get () || !jit_tls) {
        if (mono_chain_signal (SIG_HANDLER_PARAMS))
            return;
        mono_handle_native_sigsegv (SIGSEGV, ctx);
    }


    ip = mono_arch_ip_from_context (ctx);
#ifdef _WIN64
    /* Sometimes on win64 we get null IP, but the previous frame is a valid managed frame */
    /* So pop and try again */
    if (!ip && ctx)
    {
        MonoContext *context = (MonoContext*)ctx;
        gpointer *sp = context->rsp;
        if (sp)
        {
            ip = context->rip = *sp;
            context->rsp += sizeof(gpointer);
        }
    }
#endif
    ji = mono_jit_info_table_find (mono_domain_get (), ip);


#ifdef MONO_ARCH_SIGSEGV_ON_ALTSTACK
    if (mono_handle_soft_stack_ovf (jit_tls, ji, ctx, (guint8*)info->si_addr))
        return;


    /* The hard-guard page has been hit: there is not much we can do anymore
     * Print a hopefully clear message and abort.
     */
    if (jit_tls->stack_size &&
            ABS ((guint8*)info->si_addr - ((guint8*)jit_tls->end_of_stack - jit_tls->stack_size)) < 32768) {
        const char *method;
        /* we don't do much now, but we can warn the user with a useful message */
        fprintf (stderr, "Stack overflow: IP: %p, fault addr: %p\n", mono_arch_ip_from_context (ctx), (gpointer)info->si_addr);
        if (ji && ji->method)
            method = mono_method_full_name (ji->method, TRUE);
        else
            method = "Unmanaged";
        fprintf (stderr, "At %s\n", method);
        _exit (1);
    } else {
        /* The original handler might not like that it is executed on an altstack... */
        if (!ji && mono_chain_signal (SIG_HANDLER_PARAMS))
            return;


        mono_arch_handle_altstack_exception (ctx, info->si_addr, FALSE);
    }
#else


    if (!ji) {
        if (mono_chain_signal (SIG_HANDLER_PARAMS))
            return;


        mono_handle_native_sigsegv (SIGSEGV, ctx);
    }

    mono_arch_handle_exception (ctx, NULL, FALSE);
#endif
}

mono_handle_native_sigsegv

幾個(gè)關(guān)鍵點(diǎn):

  • mono_backtrace_from_context (OS X) 從sig context里轉(zhuǎn)成MonoContext, 并且回溯堆棧的每一個(gè)PC
  • backtrace (非OS X) 也是回溯堆棧的每一個(gè)PC值
  • backtrace_symbols 將每個(gè)PC值轉(zhuǎn)換成函數(shù)名(符號名稱)

然后將堆棧打印到stderr

然后通過GDB獲取更詳細(xì)的調(diào)試信息, 并打印到stderr.

最后去掉監(jiān)聽ABRT信號量, 然后調(diào)用 abort() 函數(shù)來退出程序.

Throw Exception

mono_arm_throw_exception

exceptions-arm.c

拋出異常的代碼:

void
mono_arm_throw_exception (MonoObject *exc, unsigned long eip, unsigned long esp, gulong *int_regs, gdouble *fp_regs)
{
    static void (*restore_context) (MonoContext *);
    MonoContext ctx;
    gboolean rethrow = eip & 1;


    if (!restore_context)
        restore_context = mono_get_restore_context ();


    eip &= ~1; /* clear the optional rethrow bit */
    /* adjust eip so that it point into the call instruction */
    eip -= 4;


    /*printf ("stack in throw: %p\n", esp);*/
    MONO_CONTEXT_SET_BP (&ctx, int_regs [ARMREG_FP - 4]);
    MONO_CONTEXT_SET_SP (&ctx, esp);
    MONO_CONTEXT_SET_IP (&ctx, eip);
    memcpy (((guint8*)&ctx.regs) + (4 * 4), int_regs, sizeof (gulong) * 8);
    /* memcpy (&ctx.fregs, fp_regs, sizeof (double) * MONO_SAVED_FREGS); */


    if (mono_object_isinst (exc, mono_defaults.exception_class)) {
        MonoException *mono_ex = (MonoException*)exc;
        if (!rethrow)
            mono_ex->stack_trace = NULL;
    }
    mono_handle_exception (&ctx, exc, (gpointer)(eip + 4), FALSE);
    restore_context (&ctx);
    g_assert_not_reached ();
}

保存context
mono_handle_exception
還原context

mono_handle_exception

mini-exceptions.c

  • mono_handle_exception
    • mono_handle_exception_internal

MonoContext

Mono為了做平臺兼容性, 將sig_context全部統(tǒng)一成 MonoContext 結(jié)構(gòu)體, 主要包括寄存器的各類值, 例如ARM下保存了PC, FP, SP和R0-R15

typedef struct {
    gulong eip;          // pc
    gulong ebp;          // fp
    gulong esp;          // sp
    gulong regs [16];
    double fregs [MONO_SAVED_FREGS];
} MonoContext;

eip -> sigctx.arm_pc (R15)
esp -> sigctx.arm_sp (RR13)
ebp -> sigctx.arm_fp (R11)
regs -> sigctx.arm_r0, sizeof(gulong) * 16 (R0 ~ R15)


http://www.mono-project.com/docs/debug+profile/debug/
http://www.mono-project.com/docs/advanced/embedding/

define mono_backtrace select-frame 0 set $i = 0 while ($i < $arg0) set $foo = (char*) mono_pmip ($pc) if ($foo) printf "#%d %p in %s\n", $i, $pc, $foo else frame end up-silently set $i = $i + 1 end end
define mono_stack set $mono_thread = mono_thread_current () if ($mono_thread == 0x00) printf "No mono thread associated with this thread\n" else set $ucp = malloc (sizeof (ucontext_t)) call (void) getcontext ($ucp) call (void) mono_print_thread_dump ($ucp) call (void) free ($ucp) end end

mono_chain_signal 調(diào)用原h(huán)andler
mono_handle_native_sigsegv 打印堆棧 and abort()
mono_arch_handle_exception // exceptions-arm.c
mono_handle_exception_internal // mini-exceptions.c

    if (!ji) {
        if (mono_chain_signal (SIG_HANDLER_PARAMS))
            return;


        mono_handle_native_sigsegv (SIGSEGV, ctx);
    }

    mono_arch_handle_exception (ctx, exc, FALSE);

mini.c
SIG_HANDLER_SIGNATURE (mono_sigfpe_signal_handler)
SIG_HANDLER_SIGNATURE (mono_sigill_signal_handler)
SIG_HANDLER_SIGNATURE (mono_sigsegv_signal_handler)
SIG_HANDLER_SIGNATURE (mono_sigint_signal_handler)

mini-posix.c
SIG_HANDLER_SIGNATURE (sigabrt_signal_handler)
SIG_HANDLER_SIGNATURE (sigusr1_signal_handler)
SIG_HANDLER_SIGNATURE (sigprof_signal_handler)
SIG_HANDLER_SIGNATURE (sigquit_signal_handler)
SIG_HANDLER_SIGNATURE (sigusr2_signal_handler)


在非管理線程, 無法獲取 tls, 主要是兩個(gè):

  • mono_domain_get()
  • jit_tls

就算可以通過ptrace獲取tls, 但因?yàn)楸仨氄{(diào)用如下幾個(gè)函數(shù)來walk stack,

  • mono_jit_walk_stack_from_ctx
  • mono_walk_stack

這里函數(shù)里面都去訪問了tls, 因此無法傳值進(jìn)去, 如果自己去實(shí)現(xiàn)這兩個(gè)函數(shù), 又因?yàn)楹芏嘟Y(jié)構(gòu)體無法訪問到, 因此不能自己去實(shí)現(xiàn)stack walker

就算只希望拿到最后一個(gè)pc, 去獲取c#函數(shù)名, 因?yàn)樗械腃#的函數(shù)信息都存放在 domain 里的jitInfoTable 里, 如果可以獲取到 current domain對象, 那么也可以通過
mono_jit_info_table_find(domain, addr) 函數(shù)來獲取到 MonoJitInfo, 然后用 mono_jit_info_get_method 獲取 MonoMethod, 最后通過 mono_method_full_name 來得到函數(shù)名.


NOTE ATTRIBUTES

Created Date: 2018-05-25 00:55:50
Last Evernote Update Date: 2020-05-23 07:15:03

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末月弛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子科盛,更是在濱河造成了極大的恐慌帽衙,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贞绵,死亡現(xiàn)場離奇詭異厉萝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)榨崩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門谴垫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人母蛛,你說我怎么就攤上這事翩剪。” “怎么了彩郊?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵肢专,是天一觀的道長。 經(jīng)常有香客問我焦辅,道長博杖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任筷登,我火速辦了婚禮剃根,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘前方。我一直安慰自己狈醉,他們只是感情好廉油,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苗傅,像睡著了一般抒线。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渣慕,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天嘶炭,我揣著相機(jī)與錄音,去河邊找鬼逊桦。 笑死眨猎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的强经。 我是一名探鬼主播睡陪,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼匿情!你這毒婦竟也來了兰迫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炬称,失蹤者是張志新(化名)和其女友劉穎逮矛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體转砖,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年鲸伴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了府蔗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汞窗,死狀恐怖姓赤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仲吏,我是刑警寧澤不铆,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裹唆,受9級特大地震影響誓斥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜许帐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一劳坑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧成畦,春花似錦距芬、人聲如沸涝开。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舀武。三九已至,卻和暖如春离斩,著一層夾襖步出監(jiān)牢的瞬間银舱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工捐腿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纵朋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓茄袖,卻偏偏與公主長得像操软,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子宪祥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355