首页 > 其他分享 >记录一个Mono Runtime与Mono Debugger-Agent的兼容性问题

记录一个Mono Runtime与Mono Debugger-Agent的兼容性问题

时间:2024-02-22 17:37:16浏览次数:32  
标签:Debugger state Mono CMD STATE VM 兼容性问题 mono BLOCKING

    在mono集成到C++应用程序中时,可以通过一些参数来配置mono启动时暂停,并在调试器链接上来后再继续运行。这对于调试一些启动时机非常早的代码会非常有用。这通过给mono_jit_parse_options传递参数embedding和suspend来实现。

static void
print_usage (void)
{
    PRINT_ERROR_MSG ("Usage: mono --debugger-agent=[<option>=<value>,...] ...\n");
    PRINT_ERROR_MSG ("Available options:\n");
    PRINT_ERROR_MSG ("  transport=<transport>\t\tTransport to use for connecting to the debugger (mandatory, possible values: 'dt_socket')\n");
    PRINT_ERROR_MSG ("  address=<hostname>:<port>\tAddress to connect to (mandatory)\n");
    PRINT_ERROR_MSG ("  loglevel=<n>\t\t\tLog level (defaults to 0)\n");
    PRINT_ERROR_MSG ("  logfile=<file>\t\tFile to log to (defaults to stdout)\n");
    PRINT_ERROR_MSG ("  suspend=y/n\t\t\tWhether to suspend after startup.\n");
    PRINT_ERROR_MSG ("  timeout=<n>\t\t\tTimeout for connecting in milliseconds.\n");
    PRINT_ERROR_MSG ("  server=y/n\t\t\tWhether to listen for a client connection.\n");
    PRINT_ERROR_MSG ("  keepalive=<n>\t\t\tSend keepalive events every n milliseconds.\n");
    PRINT_ERROR_MSG ("  setpgid=y/n\t\t\tWhether to call setpid(0, 0) after startup.\n");
    PRINT_ERROR_MSG ("  help\t\t\t\tPrint this help.\n");
}

       实测timeout可能与大家想象中的不一致,如果超时调试器没有链接上来,那么将不会再继续接收调试器链接请求。所以这是一个问题。

       延迟启动后,在通过mono_jit_init_version初始化Domain完成后,继续执行其它代码,此时会遇到类似下面这个错误:

mono_coop_mutex_lock Cannot transition thread 000000??0000???? from STATE_BLOCKING with DO_BLOCKING

       经过一些分析后发现,这个可能与协作式gc同步的兼容性有关系。如果延迟了Mono启动,那么调试器链接上来后,会立马执行一系列的信息获取操作,这些操作就包括: CMD_VM_GET_TYPES_FOR_SOURCE_FILE

#define CMD_VM_VERSION MDBGPROT_CMD_VM_VERSION
#define CMD_VM_SET_PROTOCOL_VERSION MDBGPROT_CMD_VM_SET_PROTOCOL_VERSION
#define CMD_VM_ALL_THREADS MDBGPROT_CMD_VM_ALL_THREADS
#define CMD_VM_SUSPEND MDBGPROT_CMD_VM_SUSPEND
#define CMD_VM_RESUME MDBGPROT_CMD_VM_RESUME
#define CMD_VM_DISPOSE MDBGPROT_CMD_VM_DISPOSE
#define CMD_VM_EXIT MDBGPROT_CMD_VM_EXIT
#define CMD_VM_INVOKE_METHOD MDBGPROT_CMD_VM_INVOKE_METHOD
#define CMD_VM_INVOKE_METHODS MDBGPROT_CMD_VM_INVOKE_METHODS
#define CMD_VM_ABORT_INVOKE MDBGPROT_CMD_VM_ABORT_INVOKE
#define CMD_VM_SET_KEEPALIVE MDBGPROT_CMD_VM_SET_KEEPALIVE
#define CMD_VM_GET_TYPES_FOR_SOURCE_FILE MDBGPROT_CMD_VM_GET_TYPES_FOR_SOURCE_FILE
#define CMD_VM_GET_TYPES MDBGPROT_CMD_VM_GET_TYPES
#define CMD_VM_START_BUFFERING MDBGPROT_CMD_VM_START_BUFFERING
#define CMD_VM_STOP_BUFFERING MDBGPROT_CMD_VM_STOP_BUFFERING
case CMD_VM_GET_TYPES_FOR_SOURCE_FILE: {
    char *fname, *basename;
    gboolean ignore_case;
    GPtrArray *res_classes, *res_domains;
            
    fname = decode_string (p, &p, end);
    ignore_case = decode_byte (p, &p, end);

    basename = dbg_path_get_basename (fname);

    res_classes = g_ptr_array_new ();
    res_domains = g_ptr_array_new ();

    mono_loader_lock ();

    t_start = clock();

    GetTypesForSourceFileArgs args;
    memset (&args, 0, sizeof (args));
    args.ignore_case = ignore_case;
    args.basename = basename;
    args.res_classes  = res_classes;
    args.res_domains = res_domains;
    mono_de_foreach_domain (get_types_for_source_file, &args);

    t_end = clock();

    mono_loader_unlock ();

    time_spent = (double)(t_end - t_start) / CLOCKS_PER_SEC;
    g_print("CMD_VM_GET_TYPES_FOR_SOURCE_FILE:%f\n", (float)time_spent);

    g_free (fname);
    g_free (basename);

    buffer_add_int (buf, res_classes->len);
    for (guint i = 0; i < res_classes->len; ++i)
        buffer_add_typeid (buf, (MonoDomain *)g_ptr_array_index (res_domains, i), (MonoClass *)g_ptr_array_index (res_classes, i));
    g_ptr_array_free (res_classes, TRUE);
    g_ptr_array_free (res_domains, TRUE);
    break;
}

     这是对应的代码,其中会对loader进行加锁。这个操作的时间可长可短。而这个操作可能会对MonoRuntime的其它代码执行产生影响。在mono_loader_lock中:

/**
 * mono_loader_lock:
 *
 * See \c docs/thread-safety.txt for the locking strategy.
 */
void
mono_loader_lock (void)
{
    mono_locks_coop_acquire (&loader_mutex, LoaderLock);
    if (G_UNLIKELY (loader_lock_track_ownership)) {
        mono_native_tls_set_value (loader_lock_nest_id, GUINT_TO_POINTER (GPOINTER_TO_UINT (mono_native_tls_get_value (loader_lock_nest_id)) + 1));
    }
}

static inline void
mono_coop_mutex_lock (MonoCoopMutex *mutex)
{
    /* Avoid thread state switch if lock is not contended */
    if (mono_os_mutex_trylock (&mutex->m) == 0)
        return;
                
    MONO_ENTER_GC_SAFE;

    mono_os_mutex_lock (&mutex->m);

    MONO_EXIT_GC_SAFE;
}

    在尝试trylock失败后,会直接进入资源竞争状态,进入之前会尝试将当前线程状态调整到GC_SAFE状态。但是这个看起来没有什么问题的操作就可能引起前面说的崩溃。在我的应用场景中,存在对mono_assembly_get_image的使用,它本身会调整线程的状态,日志如下:

[ABORT_BLOCKING][000000000000B844] STATE_BLOCKING . -> RUNNING . (0 -> 0) mono_assembly_get_image
[DO_BLOCKING][000000000000B844] RUNNING . -> STATE_BLOCKING . (0 -> 0) mono_assembly_get_image

     可以看到此时主线程已经进入STATE_BLOCKING状态,如果此时再执行其它mono api,且调试器支持线程正在因为执行CMD_VM_GET_TYPES_FOR_SOURCE_FILE而对loader加了锁,那么主线程的mono_loader_lock将尝试进入GC_SAFE状态,此时会尝试调整线程状态,然而此时线程还处于STATE_BLOCKING状态,这样前面的报错就出现了:

/*
This transitions the thread into a cooperative state where it's assumed to be suspended but can continue.

Native runtime code might want to put itself into a state where the thread is considered suspended but can keep running.
That state only works as long as the only managed state touched is blitable and was pinned before the transition.

It returns the action the caller must perform:

- Continue: Entered blocking state successfully;
- PollAndRetry: Async suspend raced and won, try to suspend and then retry;

*/
MonoDoBlockingResult
mono_threads_transition_do_blocking (MonoThreadInfo* info, const char *func)
{
    int raw_state, cur_state, suspend_count;
    gboolean no_safepoints;

retry_state_change:
    UNWRAP_THREAD_STATE (raw_state, cur_state, suspend_count, no_safepoints, info);
    switch (cur_state) {

    case STATE_RUNNING: //transition to blocked
        if (!(suspend_count == 0))
            mono_fatal_with_history ("suspend_count = %d, but should be == 0", suspend_count);
        if (no_safepoints)
            mono_fatal_with_history ("no_safepoints = TRUE, but should be FALSE in state RUNNING with DO_BLOCKING");
        if (thread_state_cas (&info->thread_state, build_thread_state (STATE_BLOCKING, suspend_count, no_safepoints), raw_state) != raw_state)
            goto retry_state_change;
        trace_state_change_sigsafe ("DO_BLOCKING", info, raw_state, STATE_BLOCKING, no_safepoints, 0, func);
        return DoBlockingContinue;

    case STATE_ASYNC_SUSPEND_REQUESTED:
        if (!(suspend_count > 0))
            mono_fatal_with_history ("suspend_count = %d, but should be > 0", suspend_count);
        if (no_safepoints)
            mono_fatal_with_history ("no_safepoints = TRUE, but should be FALSE in state ASYNC_SUSPEND_REQUESTED with DO_BLOCKING");
        trace_state_change_sigsafe ("DO_BLOCKING", info, raw_state, cur_state, no_safepoints, 0, func);
        return DoBlockingPollAndRetry;
/*
STATE_ASYNC_SUSPENDED
STATE_SELF_SUSPENDED: Code should not be running while suspended.
STATE_BLOCKING:
STATE_BLOCKING_SUSPEND_REQUESTED:
STATE_BLOCKING_SELF_SUSPENDED: Blocking is not nestabled
STATE_BLOCKING_ASYNC_SUSPENDED: Blocking is not nestable _and_ code should not be running while suspended
*/
    default:
        mono_fatal_with_history ("%s Cannot transition thread %p from %s with DO_BLOCKING", func, mono_thread_info_get_tid (info), state_name (cur_state));
    }
}

     我目前也没有什么好的办法来解决这个问题,毕竟mono不是我写的,协作gc还挺复杂的,不改乱改。目前我采用的策略是延迟Domain初始化完成后的其它代码的调用。比如退出1-2秒,确保不会跟调试器支持线程的资源竞争即可。当然这不能根治这个问题,具体细节我提交到github上让微软看去。

 

标签:Debugger,state,Mono,CMD,STATE,VM,兼容性问题,mono,BLOCKING
From: https://www.cnblogs.com/bodong/p/18027808

相关文章

  • 【Java】反应式编程 之 Mono.defer与Mono.just
    Mono.defer方法创建数据源属于懒汉型,Mono.just方法创建数据源属于恶汉型,下面看一个例子:packagecom.example.demo;importorg.junit.jupiter.api.Test;importorg.springframework.boot.test.context.SpringBootTest;importreactor.core.publisher.Mono;importjava.util......
  • Unity Mono单例(MonoSingleton)C#脚本
    什么是单例模式?单例模式是设计模式的一种,一般来说,使用单例模式的类,在程序中全局只会存在一个实例,并且一般来讲其是全局可被访问的。在unity游戏开发中,单例模式广泛应用于GameManager和各种Controller这种只需要一个实例的脚本。以下就是UnityMono单例的泛用脚本usingSystem.......
  • html 禁止f12调试代码 debugger
    setInterval(()=>{(function(a){return(function(a){return(Function('Function(arguments[0]+"'+a+'")()'))})(a)})('bugger')('de',0,0,(0,0));},1000);js代码放到HTMLscrip标签块中即可......
  • 关于gunicorn与异步兼容性问题:AttributeError: module 'select' has no attribute 'ep
    关于gunicorn与异步兼容性问题:AttributeError:module'select'hasnoattribute'epoll'背景:介绍:  在使用gunicorn、Flask&flask-sockets部署,实现websocket协议中同类消息阻塞,不同类消息不阻塞场景。异常:[2024-01-1510:22:16+0800][31655][ERROR]Ex......
  • 如何让Visual Studio Tools for Unity插件用于调试你自己的Mono嵌入应用程序
       最近在测试将mono嵌入到C++应用程序中,苦于没有调试器,有时候还是不怎么方便。网上搜了一下,有VS插件MDebug、VSMonoDebugger,实际试用了一下,有点麻烦,而且似乎对Windows+VisualStudio2022支持不大好。因此想到了,Unity引擎是基于mono的,VisualStudio2022也内置了针对Unity的......
  • 袋鼠云数栈前端从 Multirepo 到 Monorepo 研发效率提升探索之路
    我们是袋鼠云数栈UED团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。本文作者:星野困境频生前端代码管理何解?前端代码管理一直是困扰着不少前端开发团队的难题,从开发到发布的整体工作流程中,除了常规的技术问题外,往......
  • 通杀无限 debugger,目前只有 1% 的人知道!
    前言相信很多小伙伴在进行web逆向的时候,都遇到过无限debugger。最简单的方法,在debugger位置,点击行号,右键Neverpausehere,永远不在此处断下即可。但是这种方法就妄想通杀,显然是不大可能的,不然这种防护岂不是弄出来骗自己的。现在很多网站,这样处理是无法绕过的。例如常规......
  • 过瑞数中的高级debugger
    (function(){vara=newDate();debugger;returnnewDate()-a>100;}())这种debugger如果是用‘一律不在此处暂停’是过不去的。 要在浏览器的控制台中执行并阻止debugger语句,你可以使用JavaScript的toString()方法来重写原函数的定义。这种方法......
  • vins-mono 预积分
                                               ......
  • JS反调试VMdebugger
    今天干活的时候,要在前端调试找token值,打开“源代码”选项卡的时候,就不断步入“VMXXX”格式的源码当中,在查询了一些资料之后,发现Node.js反调试的一种手段,网上有说是Googlechrome加载evel()函数,应该是存在这种的情况的,不过在测试的过程当中大多数应该都是反调试的这种。该反调试的......