在Android Native层实现Try/Catch异常处理机制

文章目录

    • 一、技术原理
    • 二、代码实现
      • 2.1 定义结构体保存线程的异常处理信息
      • 2.2 实现try/catch语义
      • 2.3 检查当前线程的异常处理信息
      • 2.4 设置信号处理函数
      • 2.5 信号处理和非局部跳转
        • 2.5.1 信号处理函数实现
        • 2.5.2 跳转回用户空间
      • 2.6 清理异常处理的资源
    • 三、使用示例
      • 3.1 示例
      • 3.2 如何在Native层获取更多的异常信息
      • 3.3 限制
      • 3.4 注意事项
    • 四、如何在Native层捕获和处理C++抛出的异常
    • 五、总结

在Java层,我们可以使用try/catch语句来捕获和处理异常。然而,在Android的Native层(C/C++代码),我们并没有内置的异常处理机制。这篇文章将介绍如何在Android Native层实现类似于try/catch的异常处理机制。

一、技术原理

在Native层实现异常处理的关键在于信号处理(Signal Handling)和非局部跳转(Non-Local Jumps)。当程序发生错误(如访问非法内存、除以零等)时,操作系统会向进程发送一个信号。我们可以设置一个信号处理函数(Signal Handler),在收到信号时执行特定的代码。

非局部跳转提供了一种在程序中跳转到另一个位置的方法,而不是按照正常的控制流程执行。在C语言中,我们可以使用setjmplongjmp函数来实现非局部跳转。setjmp函数保存当前的执行上下文(包括堆栈和寄存器状态等),并返回0。longjmp函数恢复由setjmp保存的上下文,并使setjmp返回一个非零值。我们可以利用这个特性,在信号处理函数中调用longjmp,跳转到setjmp所在的位置,实现异常的捕获和处理。

二、代码实现

2.1 定义结构体保存线程的异常处理信息

首先,我们定义一个结构体native_code_handler_struct,用于保存线程的异常处理信息。这个结构体包括一个sigjmp_buf类型的变量ctx,用于保存setjmp的上下文;一个标志位ctx_is_set,表示上下文是否已经被设置;以及其他与异常处理相关的信息。

/* Thread-specific crash handler structure. */
typedef struct native_code_handler_struct {/* Restore point context. */sigjmp_buf ctx;int ctx_is_set;int reenter;/* Alternate stack. */char *stack_buffer;size_t stack_buffer_size;stack_t stack_old;/* Signal code and info. */int code;siginfo_t si;ucontext_t uc;/* Custom assertion failures. */const char *expression;const char *file;int line;/* Alarm was fired. */int alarm;
} native_code_handler_struct;native_code_handler_struct* native_code_handler_g;static native_code_handler_struct* getCrashHandler() {return native_code_handler_g;
}

2.2 实现try/catch语义

然后,我们定义了一系列的函数和宏,用于实现try/catch语义。COFFEE_TRY宏检查当前是否已经在一个try块中(通过inside函数),如果不在,则设置信号处理函数(通过setupSignalHandler函数)并保存执行上下文(通过sigsetjmp函数)。COFFEE_CATCH宏和COFFEE_END宏则用于标识catch块和try/catch块的结束。

/** Internal functions & definitions, not to be used directly. **/
#include <setjmp.h>
extern int inside(void);
extern int setupSignalHandler(int);
extern sigjmp_buf* get_ctx(void);
extern void cleanup(void);
#define COFFEE_TRY()                                \if (inside() || \(setupSignalHandler() == 0 \&& sigsetjmp(*get_ctx(), 1) == 0))
#define COFFEE_CATCH() else
#define COFFEE_END() cleanup()
/** End of internal functions & definitions. **/

2.3 检查当前线程的异常处理信息

inside函数检查当前线程的异常处理信息,如果已经在一个try块中,则增加reenter计数并返回1;否则返回0。

 * Returns 1 if we are already inside a coffeecatch block, 0 otherwise.*/
int inside() {native_code_handler_struct *const t = getCrashHandler();if (t != NULL && t->reenter > 0) {t->reenter++;return 1;}return 0;
}

2.4 设置信号处理函数

setupSignalHandler函数设置信号处理函数,并将reenter计数加1,表示进入了一个新的try块。

/*** Calls handler_setup(1) to setup a crash handler, mark the* context as valid, and return 0 upon success.*/
int setupSignalHandler(int id) {if (handler_setup(1, id) == 0) {native_code_handler_struct *const t = getCrashHandler();assert(t != NULL);t->reenter++;t->ctx_is_set = 1;LOGD("setup reenter:%d", t->reenter);return 0;} else {return -1;}
}

handler_setup设置崩溃处理器,包括全局和线程相关的资源。首先调用handler_setup_global(id)初始化全局资源,然后为当前线程初始化本地资源。

/*** Acquire the crash handler for the current thread.* The handler_cleanup() must be called to release allocated* resources.**/
static int handler_setup(int setup_thread, int id) {int code;DEBUG(print("setup for a new handler\n"));/* Initialize globals. */if (pthread_mutex_lock(&native_code_g.mutex) != 0) {return -1;}code = handler_setup_global(id);if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {return -1;}/* Global initialization failed. */if (code != 0) {return -1;}/* Initialize locals. */if (setup_thread && getCrashHandler() == NULL) {native_code_handler_struct *const t = native_code_handler_struct_init();if (t == NULL) {return -1;}native_code_handler_g = t;DEBUG(print("installed thread alternative stack\n"));}/* OK. */return 0;
}

handler_setup_global初始化全局资源,包括分配内存、设置信号处理函数等。首先为native_code_g.sa_oldnative_code_g.id分配内存,然后设置信号处理函数coffeecatch_signal_pass,并将其设置到指定的信号上。

/* Internal globals initialization. */
static int handler_setup_global(int id) {int curInitCount = native_code_g.initialized;size_t i;struct sigaction sa_pass;if (native_code_g.initialized++ == 0) {native_code_g.sa_old = calloc(sizeof(struct sigaction*), MAX_SIGNAL_HANDLER_SETUP_TIMES);if (native_code_g.sa_old == NULL) {return -1;}native_code_g.id = calloc(sizeof(int), MAX_SIGNAL_HANDLER_SETUP_TIMES);}native_code_g.id[curInitCount] = id;if (native_code_g.initialized > native_code_g.maxInitialized) {native_code_g.maxInitialized = native_code_g.initialized;assert(native_code_g.maxInitialized <= MAX_SIGNAL_HANDLER_SETUP_TIMES);}DEBUG(print("installing global signal handlers\n"));/* Setup handler structure. */memset(&sa_pass, 0, sizeof(sa_pass));sigemptyset(&sa_pass.sa_mask);sa_pass.sa_sigaction = coffeecatch_signal_pass;sa_pass.sa_flags = SA_SIGINFO | SA_ONSTACK;/* Allocate */native_code_g.sa_old[curInitCount] = calloc(sizeof(struct sigaction), SIG_NUMBER_MAX);if (native_code_g.sa_old[curInitCount] == NULL) {return -1;}/* Setup signal handlers for SIGABRT (Java calls abort()) and others. **/for (i = 0; native_sig_catch[i] != 0; i++) {const int sig = native_sig_catch[i];const struct sigaction * const action = &sa_pass;assert(sig < SIG_NUMBER_MAX);if (sigaction(sig, action, &native_code_g.sa_old[curInitCount][sig]) != 0) {return -1;}}DEBUG(print("installed global signal handlers\n"));/* OK. */return 0;
}

2.5 信号处理和非局部跳转

coffeecatch_signal_passcoffeecatch_try_jump_userland两个函数用于信号处理和非局部跳转,以实现在Java层捕获Native层的异常。

2.5.1 信号处理函数实现

coffeecatch_signal_pass是一个信号处理函数,用于在捕获到信号时执行。它首先调用原始的Java信号处理器,然后设置一个定时器以防止死锁。接着,它检查是否有可用的上下文,如果有,则将信号信息和上下文信息保存到native_code_handler_struct结构体中,并尝试跳转到用户空间。如果没有可用的上下文,函数将调用abort()终止程序。

static void coffeecatch_signal_pass(const int code, siginfo_t *const si,void *const sc) {native_code_handler_struct *t;DEBUG(print("caught signal\n"));/* Call the "real" Java handler for JIT and internals. */coffeecatch_call_old_signal_handler(code, si, sc);/* Ensure we do not deadlock. Default of ALRM is to die.* (signal() and alarm() are signal-safe) */signal(code, SIG_DFL);coffeecatch_start_alarm();/* Available context ? */t = coffeecatch_get();if (t != NULL) {/* An alarm() call was triggered. */coffeecatch_mark_alarm(t);/* Take note of the signal. */coffeecatch_copy_context(t, code, si, sc);/* Back to the future. */coffeecatch_try_jump_userland(t, code, si, sc);}/* Nope. (abort() is signal-safe) */DEBUG(print("calling abort()\n"));signal(SIGABRT, SIG_DFL);abort();
}
2.5.2 跳转回用户空间

coffeecatch_try_jump_userland尝试将程序的执行环境跳转回用户空间。它首先检查是否有有效的上下文,如果有,则恢复备用堆栈,并调用siglongjmp()函数跳转回之前保存的执行环境。

需要注意的是,siglongjmp()函数在信号处理中并不是异步信号安全的,因此在使用它时需要谨慎。

/* Try to jump to userland. */
static void coffeecatch_try_jump_userland(native_code_handler_struct*const t,const int code,siginfo_t *const si,void * const sc) {(void) si; /* UNUSED */(void) sc; /* UNUSED *//* Valid context ? */if (t != NULL && t->ctx_is_set) {DEBUG(print("calling siglongjmp()\n"));/* Invalidate the context */t->ctx_is_set = 0;/* We need to revert the alternate stack before jumping. */coffeecatch_revert_alternate_stack();siglongjmp(t->ctx, code);}
}

这段代码的主要作用是在捕获到信号时执行特定的操作,例如保存信号信息、恢复执行环境等。

2.6 清理异常处理的资源

cleanup函数清理异常处理的资源,并将reenter计数减1,表示退出了一个try块。

/*** Calls handler_cleanup()*/
void cleanup() {revert_alternate_stack();native_code_handler_struct *const t = getCrashHandler();assert(t != NULL);assert(t->reenter > 0);t->reenter--;if (t->reenter == 0) {t->ctx_is_set = 0;handler_cleanup();}
}

revert_alternate_stack() 用于恢复线程的堆栈。它通过 sigaltstack() 系统调用获取当前线程的堆栈信息,并将 SS_ONSTACK 标志位清除,表示不再使用备用堆栈。

/* Unflag "on stack" */
static void revert_alternate_stack(void) {
#ifndef NO_USE_SIGALTSTACKstack_t ss;if (sigaltstack(NULL, &ss) == 0) {ss.ss_flags &= ~SS_ONSTACK;sigaltstack (&ss, NULL);}
#endif
}

handler_cleanup() 用于清理异常处理的全局资源:

  • 释放当前线程的异常处理信息,并恢复线程的堆栈。
  • 通过 pthread_mutex_lock()pthread_mutex_unlock() 函数加锁和解锁全局资源,以保证在多线程环境中的安全性。
  • 遍历所有捕获的信号,并使用 sigaction() 函数将信号处理函数恢复为最早设置的旧信号处理函数。
  • 释放所有分配的内存,并使用 pthread_key_delete() 函数删除线程局部存储的键。
static int handler_cleanup() {/* Cleanup locals. */native_code_handler_struct *const t = getCrashHandler();if (t != NULL) {DEBUG(print("removing thread alternative stack\n"));/* Erase thread-specific value now (detach). */if (pthread_setspecific(native_code_thread, NULL) != 0) {assert(! "pthread_setspecific() failed");}/* Free handler and reset slternate stack */if (native_code_handler_struct_free(t) != 0) {return -1;}DEBUG(print("removed thread alternative stack\n"));}/* Cleanup globals. */if (pthread_mutex_lock(&native_code_g.mutex) != 0) {assert(! "pthread_mutex_lock() failed");}assert(native_code_g.maxInitialized != 0);if (native_code_g.initialized == 0) {size_t i;/* Restore signal handler. */for(i = 0; native_sig_catch[i] != 0; i++) {const int sig = native_sig_catch[i];assert(sig < SIG_NUMBER_MAX);//直接重置成最早一次设置信号处理函数时,所对应的旧信号处理函数if (sigaction(sig, &native_code_g.sa_old[0][sig], NULL) != 0) {return -1;}}/* Free old structure. */for (i = 0;i < native_code_g.maxInitialized;i++) {free(native_code_g.sa_old[i]);native_code_g.sa_old[i] = NULL;}/* Free old structure. */free(native_code_g.sa_old);native_code_g.sa_old = NULL;free(native_code_g.id);native_code_g.id = NULL;LOGV("cleanup signal handler");/* Delete thread var. */if (pthread_key_delete(native_code_thread) != 0) {assert(! "pthread_key_delete() failed");}}if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {assert(! "pthread_mutex_unlock() failed");}return 0;
}

三、使用示例

3.1 示例

上述实现允许我们从信号(如segv,sibus等)中恢复正常,就像一个Java异常一样。然而,它无法从allocator/mutexes等问题中恢复正常,但至少大多数崩溃(如空指针解引用、整数除法、栈溢出等)应该可以处理。

我们需用使用-funwind-tables编译所有的库,才可以在所有的二进制文件上获取正确的堆栈信息。在ARM上,也可以使用--no-merge-exidx-entries链接器开关,来解决堆栈相关的问题。在Android上,可以在每个库的Android.mk文件中使用以下行来实现这一点:
LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries

以下是一个简单的示例,演示如何在Android Native层使用上述代码实现的try/catch异常处理机制。

COFFEE_TRY() {call_some_native_function()
} COFFEE_CATCH() {const char*const message = get_message();jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");(*env)->ThrowNew(env, cls, strdup(message));
} COFFEE_END();

当异常发生时,程序会跳过try块中剩余的代码,直接进入catch块。这样,我们可以捕获和处理异常,避免程序崩溃。

通过上述代码,我们可以在Android Native层实现类似于Java的try/catch异常处理机制。这对于提高Native代码的稳定性和可维护性非常有帮助。需要注意的是,这种方法并不能捕获所有类型的异常,例如C++抛出的异常。在实际应用中,我们需要根据具体的需求和场景来选择最合适的异常处理策略。

3.2 如何在Native层获取更多的异常信息

我们还可以在catch块中获取和处理这些异常信息。例如,打印异常类型、出错地址、寄存器状态等。

const char* get_message() {const int error = errno; // 保存当前线程的 errno 值const native_code_handler_struct* const t = getCrashHandler(); // 获取当前线程的异常处理信息// 如果找到有效的异常处理信息if (t != NULL) {char * const buffer = t->stack_buffer; // 缓冲区用于存储错误消息const size_t buffer_len = t->stack_buffer_size; // 缓冲区的大小size_t buffer_offs = 0; // 缓冲区的偏移量,用于追加字符串const char* const posix_desc =desc_sig(t->si.si_signo, t->si.si_code); // 获取信号的描述字符串// 如果是断言失败if ((t->code == SIGABRT
#ifdef __ANDROID__// 在 Android 系统中,由于 BUG #16672,断言失败可能会导致 SIGSEGV 信号|| (t->code == SIGSEGV && (uintptr_t) t->si.si_addr == 0xdeadbaad)
#endif) && t->expression != NULL) {// 将断言失败的信息格式化到缓冲区snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,"assertion '%s' failed at %s:%d",t->expression, t->file, t->line);buffer_offs += strlen(&buffer[buffer_offs]);}// 其他信号else {// 将信号编号格式化到缓冲区snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, "signal %d", t->si.si_signo);buffer_offs += strlen(&buffer[buffer_offs]);// 将信号描述格式化到缓冲区snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " (%s)", posix_desc);buffer_offs += strlen(&buffer[buffer_offs]);// 如果是非法指令或段错误,将错误地址格式化到缓冲区if (t->si.si_signo == SIGILL || t->si.si_signo == SIGSEGV) {snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " at address %p", t->si.si_addr);buffer_offs += strlen(&buffer[buffer_offs]);}}// 如果信号关联的 errno 值非零,将对应的错误信息格式化到缓冲区if (t->si.si_errno != 0) {snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, ": ");buffer_offs += strlen(&buffer[buffer_offs]);if (strerror_r(t->si.si_errno, &buffer[buffer_offs], buffer_len - buffer_offs) == 0) {snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, "unknown error");buffer_offs += strlen(&buffer[buffer_offs]);}}// 如果是子进程终止信号,将发送进程的 ID 格式化到缓冲区if (t->si.si_signo == SIGCHLD && t->si.si_pid != 0) {snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " (sent by pid %d)", (int) t->si.si_pid);buffer_offs += strlen(&buffer[buffer_offs]);}// 返回错误消息字符串buffer[buffer_offs] = '\0';return t->stack_buffer;} else {// 静态缓冲区用于处理异常处理器设置期间的错误static char buffer[256];
#ifdef _GNU_SOURCEreturn strerror_r(error, &buffer[0], sizeof(buffer));
#elseconst int code = strerror_r(error, &buffer[0], sizeof(buffer));errno = error;if (code == 0) {return buffer;} else {return "unknown error during crash handler setup";}
#endif}
}

这个函数的主要作用是在捕获到异常时获取异常的详细信息,以便在异常处理代码中使用。通过这个函数,我们可以在Android Native层实现更详细和准确的异常处理。

需要注意的是,在处理异常时,我们应该尽量避免执行可能触发新异常的操作,例如访问非法内存、调用不安全的函数等。在实际应用中,我们可以根据具体的需求和场景来选择最合适的异常处理策略。

3.3 限制

  1. 本文提供的异常处理机制不能捕获所有类型的异常。例如,不能捕获由于堆栈溢出导致的异常。对于这些情况,需要使用其他方法来进行处理和调试。

  2. 在某些架构和编译器下,setjmplongjmp函数的行为可能与本文描述的不完全相同。因此在使用本文提供的异常处理机制之前,请确保在目标平台上能够正常工作。

  3. 本文提供的异常处理机制可能会影响应用程序的性能。因为它需要在运行时设置信号处理函数,并在发生异常时执行非局部跳转。在性能敏感的场景中,请谨慎使用这种机制。

3.4 注意事项

  1. 在使用本文提供的异常处理机制时,请确保正确地设置和清理信号处理函数。在多线程环境中,需要为每个线程单独设置和清理信号处理函数。

  2. 在catch块中,尽量避免执行可能引发新异常的代码。因为在catch块中发生的异常可能无法被捕获和处理。

  3. 在catch块中,可以使用COFFEE_EXCEPTION()宏获取异常的详细信息,例如信号编号、错误地址等。这些信息对于调试和错误报告非常有用。

  4. 请注意,本文提供的异常处理机制并不能替代合理的错误处理和资源管理策略。在编写Native代码时,请始终确保正确地处理错误情况,并在适当的时候释放分配的资源。

四、如何在Native层捕获和处理C++抛出的异常

在前面的部分中,我们已经介绍了如何在Android Native层实现类似于Java的try/catch异常处理机制,并获取异常的详细信息。现在,我们将介绍如何在Native层捕获和处理C++抛出的异常。

在C++中,异常处理机制与C语言中的信号处理和非局部跳转不同。C++异常是通过throw语句抛出的,可以被catch语句捕获和处理。由于C++异常处理机制与C语言不兼容,我们需要使用C++特性来捕获和处理C++异常。

以下是一个简单的示例,演示如何在Android Native层捕获和处理C++抛出的异常:

#include <iostream>
#include <stdexcept>void native_function() {try {// 故意抛出一个C++异常throw std::runtime_error("An error occurred.");std::cout << "This line will not be executed." << std::endl;} catch (const std::exception &e) {std::cout << "Caught an exception: " << e.what() << std::endl;} catch (...) {std::cout << "Caught an unknown exception." << std::endl;}
}

在这个示例中,我们使用C++的trycatch语句捕获和处理异常。当发生异常时,程序会跳过try块中剩余的代码,直接进入catch块。这样,我们可以捕获和处理C++抛出的异常,避免程序崩溃。

需要注意的是,C++异常处理机制与前面介绍的C语言异常处理机制不兼容。在混合使用C和C++代码的项目中,我们需要分别处理C和C++的异常。在实际应用中,我们可以根据具体的需求和场景来选择最合适的异常处理策略。

五、总结

总结一下,在Android Native层实现异常处理机制,我们需要考虑以下几点:

  1. 使用信号处理和非局部跳转实现类似于Java的try/catch异常处理机制,捕获C语言中的异常(如非法内存访问、浮点异常等)。

  2. 在信号处理函数中获取异常的详细信息(如信号类型、出错地址、寄存器状态等),并在catch块中进行处理。

  3. 对于C++抛出的异常,使用C++的try/catch语句进行捕获和处理。

通过以上方法,我们可以在Android Native层实现更稳定和可维护的代码。在实际应用中,我们需要根据具体的需求和场景来选择最合适的异常处理策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/836637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

页眉如何插入横线

双击激活页眉——菜单栏——格式——边框和底纹——边框——应用于&#xff1a;段落——线型&#xff1a;选双线——预览&#xff1a;”点“下边框——确定。

Ubuntu20.04右键打不开终端

今天用virtualbox安装了ubuntu20.04 问题&#xff1a;右键打开终端&#xff0c;怎么也打开不了&#xff01; 点了也没反应&#xff0c;或者鼠标转小圈圈&#xff0c;然后也没有反应… 解决方法&#xff1a; 1、Ctrl Alt F6 先切换到终端访问界面 mac电脑 Ctrl Alt F6 …

letcode整数反转

整数反转 问题 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 示例 示…

vuerouter声明式导航

声明式导航-跳转传参数 1.查询参数传参 语法&#xff1a;to /path?参数名值 2.对应页面组件接受传来的值 $router.query.参数名 2.动态路由传参 1.配置动态路由 2.配置导航连接 to/path/参数值 3.对应页面组件接收传递过来的值 #route.params.参数名 多个参数传递&…

情感感知OCR:整合深度学习技术提升文字识别系统的情感理解能力

摘要&#xff1a;随着深度学习技术的发展&#xff0c;文字识别&#xff08;OCR&#xff09;系统在识别准确率和速度上取得了长足的进步。然而&#xff0c;在处理文本时&#xff0c;仅仅依靠字符和词语的识别并不足以满足用户对信息的全面理解需求。本文提出了一种新颖的方法&am…

母亲节祝福html源码示例

创建一个完整的HTML页面&#xff0c;我可以为您提供一个简单的HTML模板&#xff0c;其中包含一些示例性的祝福语&#xff0c;并添加一些注释以帮助您理解如何扩展内容。 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8&qu…

Redis五大基本数据类型介绍及其使用场景

文章目录 1 String&#xff08;字符串&#xff09;应用场景 2 List&#xff08;列表&#xff09;应用场景 3 Set&#xff08;集合&#xff09;4 sorted set&#xff08;有序集合&#xff09;应用场景 5 hash&#xff08;哈希&#xff09;应用场景 Redis 是一个开源&#xff0c;…

彩信群发推广:四大革新优势,引领营销新时代!

在数字化营销日益盛行的今天&#xff0c;短信群发已成为我们生活中不可或缺的一部分。然而&#xff0c;您是否想过&#xff0c;除了传统的文本短信&#xff0c;还有一种更为丰富、更具吸引力的推广方式——彩信群发推广&#xff1f;彩信不仅融合了图片、文字、音频、动画和视频…

【漏洞复现】泛微OA E-Cology ln.FileDownload文件读取漏洞

漏洞描述&#xff1a; 泛微OA E-Cology是一款面向中大型组织的数字化办公产品&#xff0c;它基于全新的设计理念和管理思想&#xff0c;旨在为中大型组织创建一个全新的高效协同办公环境。泛微OA E-Cology ln.FileDownload存在任意文件读取漏洞&#xff0c;允许未经授权的用户…

轨迹规划 | 图解纯追踪算法Pure Pursuit(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 纯追踪算法原理推导2 自适应纯追踪算法(APP)3 规范化纯追踪算法(RPP)4 仿真实现4.1 ROS C仿真4.2 Python仿真4.3 Matlab仿真 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划…

快速配置 Nginx 来实现 GPT 流式传输

目录 1. Nginx 参考配置2. Nginx 核心参数3. 其他参数 场景&#xff1a;代理 ChatGPT、代理各种 GPT 工具套壳等。 1. Nginx 参考配置 支持 GPT 流式访问的配置如下&#xff0c;请根据实际需求适当取舍即可&#xff1a; server {listen 80;server_name chat.test.com; # 绑…

如何推动物联网的未来?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网作为连接物理世界和数字世界的桥梁&#xff0c;正逐渐改变着我们的生活和工作方式。在工业领域&#xff0c;物联网技术的应用不仅提高了生产效率&#xff0c;还实现了对设备的智能化管理和维护。而工业网关作为物联网的重要组成部分…

js通过时间对JSON中的数据进行排序

需求 现在需要通过每一个数据段的date字段对数组的整体数据进行排序&#xff01; 元数据如下&#xff1a; var data [{"filename": "123","date": "2024-05-10 19:53:57","stand": "GB-14","filter":…

java数据保留几位小数的问题

String.format()&#xff1a; 这个方法在格式化输出时非常常用&#xff0c;可以方便地控制小数位数&#xff0c;并且语法简洁易懂。它不仅可以用于格式化浮点数和双精度数&#xff0c;还可以用于格式化其他数据类型&#xff0c;如整数、字符串等。 示例代码&#xff1a; BigD…

仪器校准中,CNAS对报告的认可评审要求有哪些?

CNAS扩项时&#xff0c;常常会有关于对报告认可的相关要求&#xff0c;而这些要求往往有明确规范&#xff0c;那么在仪器校准中&#xff0c;CNAS对报告的认可评审要求有哪些&#xff1f; 现场评审时&#xff0c;评审组应关注抽查报告&#xff0c;评审组将现场随机抽取报告&…

thinkphp5实现多数据库连接

思路&#xff1a; 创建第二个数据库连接&#xff0c;然后在使用第二个数据的时候&#xff0c;切换连接 首先到config配置文件下创建连接 //数据库配置2,配置第二个数据库db_config2 > [// 数据库类型type > mysql,// 服务器地址hostname > 127.0.0.1,// 数据库名data…

程序员之虚拟形象课程录制

如果你实在是不想真人出镜&#xff0c;试试虚拟形象吧&#xff01; 虚拟形象类课程片段的制作 虚拟形象的原理 现在&#xff0c;我们来探讨一下虚拟形象类课程片段的制作。虚拟形象在形式上与真人出镜相似&#xff0c;但它采用卡通形象代替真人&#xff0c;更具趣味性&#x…

酷开科技让你能够放心地把遥控器交给孩子

我国电视从诞生至今已有65个年头&#xff0c;从黑白到彩色&#xff0c;从背投到液晶&#xff0c;电视的外观随着时代技术的发展而不断变化&#xff0c;直到现在随着技术的不断迭代&#xff0c;电视的功能越来越丰富&#xff0c;电视在客厅中的地位也越来越凸显。作为家庭娱乐的…

【Linux基础】Vim保姆级一键配置教程(手把手教你把Vim打造成高效率C++开发环境)

目录 一、前言 二、安装Vim 三、原始Vim编译器的缺陷分析 四、Vim配置 &#x1f95d;预备知识----.vimrc 隐藏文件 &#x1f34b;手动配置 Vim --- &#xff08;不推荐&#xff09; &#x1f347;自动化一键配置 Vim --- (强烈推荐) ✨功能演示 五、共勉 一、前言 Vim作为…

几种监控工具学习

在Linux上有很多监控工具&#xff0c;比如Zabbix、Prometheus、APM和ELK 监控工具是确保系统稳定运行的关键组件之一&#xff0c;它可以帮助系统管理员和开发人员及时发现并解决问题。 以下是几种流行的监控工具的简要介绍&#xff1a; Zabbix&#xff1a; Zabbix 是一个企…