如何在程序异常退出前输出当前进程的堆栈信息 Backtraces

作者&投稿:频狗 (若有异议请与网页底部的电邮联系)
如何在进程崩溃后打印堆栈并防止数据丢失~

进程在运行过程中遇到逻辑错误, 比如除零, 空指针等等, 系统会触发一个软件中断.
这个中断会以信号的方式通知进程, 这些信号的默认处理方式是结束进程.
发生这种情况, 我们就认为进程崩溃了.

进程崩溃后, 我们会希望知道它是为何崩溃的, 是哪个函数, 哪行代码引起的错误.
另外, 在进程退出前, 我们还希望做一些善后处理, 比如把某些数据存入数据库, 等等.

下面, 我会介绍一些技术来达成这两个目标.

1. 在core文件中查看堆栈信息

如果进程崩溃时, 我们能看到当时的堆栈信息, 就能很快定位到错误的代码.
在 gcc 中加入 -g 选项, 可执行文件中便会包含调试信息. 进程崩溃后, 会生成一个 core 文件.
我们可以用 gdb 查看这个 core 文件, 从而知道进程崩溃时的环境.

在调试阶段, core文件能给我们带来很多便利. 但是在正式环境中, 它有很大的局限:
1. 包含调试信息的可执行文件会很大. 并且运行速度也会大幅降低.
2. 一个 core 文件常常很大, 如果进程频繁崩溃, 硬盘资源会变得很紧张.

所以, 在正式环境中运行的程序, 不会包含调试信息.
它的core文件的大小, 我们会把它设为0, 也就是不会输入core文件.
在这个前提下, 我们如何得到进程的堆栈信息呢?

2. 动态获取线程的堆栈

c 语言提供了 backtrace 函数, 通过这个函数可以动态的获取当前线程的堆栈.
要使用 backtrace 函数, 有两点要求:
1. 程序使用的是 ELF 二进制格式.
2. 程序连接时使用了 -rdynamic 选项.
-rdynamic可用来通知链接器将所有符号添加到动态符号表中, 这些信息比 -g 选项的信息要少得多.

下面是将要用到的函数说明:
#include

int backtrace(void **buffer,int size);
用于获取当前线程的调用堆栈, 获取的信息将会被存放在buffer中, 它是一个指针列表。
参数 size 用来指定buffer中可以保存多少个void* 元素。
函数返回值是实际获取的指针个数, 最大不超过size大小
注意: 某些编译器的优化选项对获取正确的调用堆栈有干扰,
另外内联函数没有堆栈框架; 删除框架指针也会导致无法正确解析堆栈内容;

char ** backtrace_symbols (void *const *buffer, int size)
把从backtrace函数获取的信息转化为一个字符串数组.
参数buffer应该是从backtrace函数获取的指针数组,
size是该数组中的元素个数(backtrace的返回值) ;
函数返回值是一个指向字符串数组的指针, 它的大小同buffer相同.
每个字符串包含了一个相对于buffer中对应元素的可打印信息.
它包括函数名,函数的偏移地址, 和实际的返回地址.
该函数的返回值是通过malloc函数申请的空间, 因此调用者必须使用free函数来释放指针.
注意: 如果不能为字符串获取足够的空间, 函数的返回值将会为NULL.

void backtrace_symbols_fd (void *const *buffer, int size, int fd)
与backtrace_symbols 函数具有相同的功能,
不同的是它不会给调用者返回字符串数组, 而是将结果写入文件描述符为fd的文件中,每个函数对应一行.

3. 捕捉信号

我们希望在进程崩溃时打印堆栈, 所以我们需要捕捉到相应的信号. 方法很简单.
#include
void (*signal(int signum,void(* handler)(int)))(int);   
或者: typedef void(*sig_t) ( int );   
sig_t signal(int signum,sig_t handler);   
参数说明:  
第一个参数signum指明了所要处理的信号类型,它可以是除了SIGKILL和SIGSTOP外的任何一种信号。  
第二个参数handler描述了与信号关联的动作,它可以取以下三种值:   
1. 一个返回值为正数的函数的地址, 也就是我们的信号处理函数.
这个函数应有如下形式的定义: int func(int sig);   sig是传递给它的唯一参数。
执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行func()函数。
当func()函数执行结束后,控制权返回进程被中断的那一点继续执行。  
2. SIGIGN, 忽略该信号.
3. SIGDFL, 恢复系统对信号的默认处理。
返回值: 返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。   

注意:   
当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,
直到信号处理函数执行完毕再重新调用相应的处理函数。
如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。  
在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,
如果要改变此操作请改用sigaction()。  

4. 实例

下面我们实际编码, 看看具体如何在捕捉到信号后, 打印进程堆栈, 然后结束进程.

#include
#include
#include
#include
#include
#include
#include

using namespace std;

map SIG_LIST;

#define SET_SIG(sig) SIG_LIST[sig] = #sig;

void SetSigList(){
SIG_LIST.clear();
SET_SIG(SIGILL)//非法指令
SET_SIG(SIGBUS)//总线错误
SET_SIG(SIGFPE)//浮点异常
SET_SIG(SIGABRT)//来自abort函数的终止信号
SET_SIG(SIGSEGV)//无效的存储器引用(段错误)
SET_SIG(SIGPIPE)//向一个没有读用户的管道做写操作
SET_SIG(SIGTERM)//软件终止信号
SET_SIG(SIGSTKFLT)//协处理器上的栈故障
SET_SIG(SIGXFSZ)//文件大小超出限制
SET_SIG(SIGTRAP)//跟踪陷阱
}

string& GetSigName(int sig){
return SIG_LIST[sig];
}

void SaveBackTrace(int sig){
//打开文件
time_t tSetTime;
time(&tSetTime);
tm* ptm = localtime(&tSetTime);
char fname[256] = {0};
sprintf(fname, "core.%d-%d-%d_%d_%d_%d",
ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
FILE* f = fopen(fname, "a");
if (f == NULL){
exit(1);
}
int fd = fileno(f);

//锁定文件
flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
fl.l_pid = getpid();
fcntl(fd, F_SETLKW, &fl);

//输出程序的绝对路径
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
int count = readlink("/proc/self/exe", buffer, sizeof(buffer));
if(count > 0){
buffer[count] = '
';
buffer[count + 1] = 0;
fwrite(buffer, 1, count+1, f);
}

//输出信息的时间
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "Dump Time: %d-%d-%d %d:%d:%d
",
ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
fwrite(buffer, 1, strlen(buffer), f);

//线程和信号
sprintf(buffer, "Curr thread: %d, Catch signal:%s
",
pthread_self(), GetSigName(sig).c_str());
fwrite(buffer, 1, strlen(buffer), f);

//堆栈
void* DumpArray[256];
int nSize = backtrace(DumpArray, 256);
sprintf(buffer, "backtrace rank = %d
", nSize);
fwrite(buffer, 1, strlen(buffer), f);
if (nSize > 0){
char** symbols = backtrace_symbols(DumpArray, nSize);
if (symbols != NULL){
for (int i=0; i<nSize; i++){
fwrite(symbols[i], 1, strlen(symbols[i]), f);
fwrite("
", 1, 1, f);
}
free(symbols);
}
}

//文件解锁后关闭, 最后终止进程
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl);
fclose(f);
exit(1);
}

void SetSigCatchFun(){
map::iterator it;
for (it=SIG_LIST.begin(); it!=SIG_LIST.end(); it++){
signal(it->first, SaveBackTrace);
}
}

void Fun(){
int a = 0;
int b = 1 / a;
}

static void* ThreadFun(void* arg){
Fun();
return NULL;
}

int main(){
SetSigList();
SetSigCatchFun();

printf("main thread id = %d
", (pthread_t)pthread_self());
pthread_t pid;
if (pthread_create(&pid, NULL, ThreadFun, NULL)){
exit(1);
}
printf("fun thread id = %d
", pid);

for(;;){
sleep(1);
}
return 0;
}

文件名为 bt.cpp
编译: g++ bt.cpp -rdynamic -I /usr/local/include -L /usr/local/lib -pthread -o bt


主线程创建了 fun 线程, fun 线程有一个除零错误, 系统抛出 SIGFPE 信号.
该信号使 fun 线程中断, 我们注册的 SaveBackTrace 函数捕获到这个信号, 打印相关信息, 然后终止进程.
在输出的core文件中, 我们可以看到简单的堆栈信息.

5. 善后处理

在上面的例子中, fun 线程被 SIGFPE 中断, 转而执行 SaveBackTrace 函数.
此时, main 线程仍然在正常运行.
如果我们把 SaveBackTrace 函数最后的 exit(1); 替换成 for(;;)sleep(1);
main 线程就可以一直正常的运行下去.
利用这个特点, 我们可以做很多其它事情.

游戏的服务器进程常常有这些线程:
网络线程, 数据库线程, 业务处理线程. 引发逻辑错误的代码常常位于业务处理线程.
而数据库线程由于功能稳定, 逻辑简单, 是十分强壮的.
那么, 如果业务处理线程有逻辑错误, 我们捕捉到信号后, 可以在信号处理函数的最后,
通知数据库线程保存游戏数据.
直到数据库线程把游戏信息全部存入数据库, 信号处理函数才返回.
这样, 服务器宕机不会导致回档, 损失被大大降低.

要实现这个机制, 要求数据库模块和业务处理模块具有低耦合度.
当然, 实际应用的时候, 还有许多细节要考虑.
比如, 业务处理线程正在处理玩家的数据, 由于发生不可预知的错误, 玩家的数据被损坏了, 这些玩家的数据就不应该被存入数据库.

这个需要用调试器才可以看到的。
linux平台,一般使用gdb
windows平台一般使用windbg
加载进程后,可以在堆栈窗口看到堆栈的内容的。

打印堆栈是调试的常用方法,一般在系统异常时,我们可以将异常情况下的堆栈打印出来,这样十分方便错误查找。实际上还有另外一个非常有用的功能:分析代码的行为。android代码太过庞大复杂了,完全的静态分析经常是无从下手,因此通过打印堆栈的动态分析也十分必要。

Android打印堆栈的方法,简单归类一下
1. zygote的堆栈dump
实际上这个可以同时dump java线程及native线程的堆栈,对于java线程,java堆栈和native堆栈都可以得到。
使用方法很简单,直接在adb shell或串口中输入:
[plain] view plaincopy
kill -3 <pid>
输出的trace会保存在 /data/anr/traces.txt文件中。这个需要注意,如果没有 /data/anr/这个目录或/data/anr/traces.txt这个文件,需要手工创建一下,并设置好读写权限。
如果需要在代码中,更容易控制堆栈的输出时机,可以用以下命令获取zygote的core dump:
[java] view plaincopy
Process.sendSignal(pid, Process.SIGNAL_QUIT);
原理和命令行是一样的。
不过需要注意两点:
adb shell可能会没有权限,需要root。
android 4.2中关闭了native thread的堆栈打印,详见 dalvik/vm/Thread.cpp的dumpNativeThread方法:
[cpp] view plaincopy
dvmPrintDebugMessage(target,
"\"%s\" sysTid=%d nice=%d sched=%d/%d cgrp=%s\n",
name, tid, getpriority(PRIO_PROCESS, tid),
schedStats.policy, schedStats.priority, schedStats.group);
dumpSchedStat(target, tid);
// Temporarily disabled collecting native stacks from non-Dalvik
// threads because sometimes they misbehave.
//dvmDumpNativeStack(target, tid);
Native堆栈的打印被关掉了!不过对于大多数情况,可以直接将这个注释打开。

2. debuggerd的堆栈dump
debuggerd是android的一个daemon进程,负责在进程异常出错时,将进程的运行时信息dump出来供分析。debuggerd生 成的coredump数据是以文本形式呈现,被保存在 /data/tombstone/ 目录下(名字取的也很形象,tombstone是墓碑的意思),共可保存10个文件,当超过10个时,会覆盖重写最早生成的文件。从4.2版本开 始,debuggerd同时也是一个实用工具:可以在不中断进程执行的情况下打印当前进程的native堆栈。使用方法是:
[plain] view plaincopy
debuggerd -b <pid>
这可以协助我们分析进程执行行为,但最最有用的地方是:它可以非常简单的定位到native进程中锁死或错误逻辑引起的死循环的代码位置。

3. java代码中打印堆栈
Java代码打印堆栈比较简单, 堆栈信息获取和输出,都可以通过Throwable类的方法实现。目前通用的做法是在java进程出现需要注意的异常时,打印堆栈,然后再决定退出或挽救。通常的方法是使用exception的printStackTrace()方法:
[java] view plaincopy
try {
...
} catch (RemoteException e) {
e.printStackTrace();
...
}
当然也可以只打印堆栈不退出,这样就比较方便分析代码的动态运行情况。Java代码中插入堆栈打印的方法如下:
[java] view plaincopy
Log.d(TAG,Log.getStackTraceString(new Throwable()));

4. C++代码中打印堆栈
C++也是支持异常处理的,异常处理库中,已经包含了获取backtrace的接口,Android也是利用这个接口来打印堆栈信息的。在Android的C++中,已经集成了一个工具类CallStack,在libutils.so中。使用方法:
[cpp] view plaincopy
#include <utils/CallStack.h>
...
CallStack stack;
stack.update();
stack.dump();
使用方式比较简单。目前Andoid4.2版本已经将相关信息解析的很到位,符号表查找,demangle,偏移位置校正都做好了。
[plain] view plaincopy

5. C代码中打印堆栈
C代码,尤其是底层C库,想要看到调用的堆栈信息,还是比较麻烦的。 CallStack肯定是不能用,一是因为其实C++写的,需要重新封装才能在C中使用,二是底层库反调上层库的函数,会造成链接器循环依赖而无法链接。 不过也不是没有办法,可以通过android工具类CallStack实现中使用的unwind调用及符号解析函数来处理。
这里需要注意的是,为解决链接问题,最好使用dlopen方式,查找需要用到的接口再直接调用,这样会比较简单。如下为相关的实现代码,只需要在要 打印的文件中插入此部分代码,然后调用getCallStack()即可,无需包含太多的头文件和修改Android.mk文件:
[cpp] view plaincopy
#define MAX_DEPTH 31
#define MAX_BACKTRACE_LINE_LENGTH 800
#define PATH "/system/lib/libcorkscrew.so"

typedef ssize_t (*unwindFn)(backtrace_frame_t*, size_t, size_t);
typedef void (*unwindSymbFn)(const backtrace_frame_t*, size_t, backtrace_symbol_t*);
typedef void (*unwindSymbFreeFn)(backtrace_symbol_t*, size_t);

static void *gHandle = NULL;

static int getCallStack(void){
ssize_t i = 0;
ssize_t result = 0;
ssize_t count;
backtrace_frame_t mStack[MAX_DEPTH];
backtrace_symbol_t symbols[MAX_DEPTH];

unwindFn unwind_backtrace = NULL;
unwindSymbFn get_backtrace_symbols = NULL;
unwindSymbFreeFn free_backtrace_symbols = NULL;

// open the so.
if(gHandle == NULL) gHandle = dlopen(PATH, RTLD_NOW);

// get the interface for unwind and symbol analyse
if(gHandle != NULL) unwind_backtrace = (unwindFn)dlsym(gHandle, "unwind_backtrace");
if(gHandle != NULL) get_backtrace_symbols = (unwindSymbFn)dlsym(gHandle, "get_backtrace_symbols");
if(gHandle != NULL) free_backtrace_symbols = (unwindSymbFreeFn)dlsym(gHandle, "free_backtrace_symbols");

if(!gHandle ||!unwind_backtrace ||!get_backtrace_symbols || !free_backtrace_symbols ){
ALOGE("Error! cannot get unwind info: handle:%p %p %p %p",
gHandle, unwind_backtrace, get_backtrace_symbols, free_backtrace_symbols );
return result;
}

count= unwind_backtrace(mStack, 1, MAX_DEPTH);
get_backtrace_symbols(mStack, count, symbols);

for (i = 0; i < count; i++) {
char line[MAX_BACKTRACE_LINE_LENGTH];

const char* mapName = symbols[i].map_name ? symbols[i].map_name : "<unknown>";
const char* symbolName =symbols[i].demangled_name ? symbols[i].demangled_name : symbols[i].symbol_name;
size_t fieldWidth = (MAX_BACKTRACE_LINE_LENGTH - 80) / 2;

if (symbolName) {
uint32_t pc_offset = symbols[i].relative_pc - symbols[i].relative_symbol_addr;
if (pc_offset) {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s+%u)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName, pc_offset);
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName);
}
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s",
i, symbols[i].relative_pc, fieldWidth, mapName);
}

ALOGD("%s", line);
}

free_backtrace_symbols(symbols, count);

return result;
}
对sched_policy.c的堆栈调用分析如下,注意具体是否要打印,在哪里打印,还可以通过pid、uid、property等来控制一下,这样就不会被淹死在trace的汪洋大海中。
[plain] view plaincopy
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00010e82 /system/lib/libutils.so (androidSetThreadPriority+61)
D/SchedPolicy( 1350): #03 pc 00068104 /system/lib/libandroid_runtime.so (android_os_Process_setThreadPriority(_JNIEnv*, _jobject*, int, int)+7)
D/SchedPolicy( 1350): #04 pc 0001e510 /system/lib/libdvm.so (dvmPlatformInvoke+112)
D/SchedPolicy( 1350): #05 pc 0004d6aa /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+417)
D/SchedPolicy( 1350): #06 pc 00027920 /system/lib/libdvm.so
D/SchedPolicy( 1350): #07 pc 0002b7fc /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
D/SchedPolicy( 1350): #08 pc 00060c30 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+271)
D/SchedPolicy( 1350): #09 pc 0004cd34 /system/lib/libdvm.so
D/SchedPolicy( 1350): #10 pc 00049382 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #11 pc 00065e52 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #12 pc 0001435e /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+57)
D/SchedPolicy( 1350): #13 pc 00016f5a /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+513)
D/SchedPolicy( 1350): #14 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #15 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #16 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #17 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #18 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #19 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #20 pc 0000dac4 /system/lib/libc.so (pthread_create+160)
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00016f26 /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+461)
D/SchedPolicy( 1350): #03 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #04 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #05 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #06 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #07 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #08 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #09 pc 0000dac4 /system/lib/libc.so (pthread_create+160)

6. 其它堆栈信息查询


高手何在 ?JAVA题 求助各位 感谢~~~阅读下列程序,写出程序运行...
pas.nextToken();{已经是static了} pas

请问:长按老年机全网通应用图标不弹出选项(关闭、退出),原因何在...
老年机应用图标不支持:有些老年机可能仅支持特定类型的应用,而这些应用不一定都支持长按图标触发菜单的功能。因此,这也可能是特定应用的问题。系统或应用错误:如果长按应用图标没有弹出选项,这也可能是系统或应用程序的错误或故障。如果您想解决这个问题,可以考虑以下步骤:查看用户手册:查看您的老年...

为什么有的程序可以用ALT+F4退出,而有的就不行??原因何在?详细介绍,谢 ...
有的程序屏蔽了alt+F4这个命令,所以不能关闭,这是在编程的时候实现的

关于android2.2模拟器,运行程序时总是报错,不知问题何在
请检查指定的AVD Target是否已经设置了相应的特性。通常,缺省设置的AVD不包括键盘、轨迹球、加速度感应、GPS、相机、SD卡、电池等功能的支持。你必须在AVD管理器中手工添加相应功能,并且在调试运行应用程序时,指定该AVD Target。

我的电脑开机后一敲键盘便死机,原因何在?高手指点
发出鸣叫声但计算机不工作、或在开机自检时出现错误提示等; ②在启动计算机操作系统时发生死机:屏幕显示计算机自检通过,但在装入操作系统时,计算机出现死机的情况; ③在使用一些应用程序过程中出现死机:计算机一直都运行良好,只在执行某些应用程序时出现死机的情况; ④退出操作系统时出现死机:就是在退出Win98等系统或...

瞎写的一组c语言程序,请问问题何在
有可能是*(i+j)=z;出问题了,你想想你申请的i指针并没有给它相应足够的空间,你觉得i对应地址后的空间都没有使用?你这样指针操作是危险的 你可以这样命名 int i[10];这样可以保证n不大于10是正常的。如果你想弄成活的,那就要用到链表的数据结构了 ...

没有可用于中央设备选件的数据记录,西门子1500PLC,什么情况?麻烦指导...
原因:用了CPU的组态控制功能,没有在启动OB中执行指令,WRREC 指令在启动 OB 中传送完控制数据记录后组态控制才会生效。如果已启用组态控制但 CPU 不具有控制数据记录,则在退出 STARTUP 模式时会转到 STOP 模式。所以需要确保启动 OB 中包含传输控制数据记录的程序。解决方法如下:PLC属性---组态控制-...

行政程序该不该立法,其意义与价值何在
该不该立法,这是无需讨论的,必须立法加以规范。所谓行政程序是指“行政主体的行政行为在时间和空间上的表现形式,即行政行为所遵循的方式、步骤、顺序以及时限的总和” 。因此,行政程序是就行政主体的行政行为而言的,是行政主体实施行政行为时所包含或经历的过程。法律程序是人治与法治的分界限。公正...

怪物猎人3卡住
A:尝试关闭音轨;在运行游戏前退出任何会突然占用内存和CPU的程序(如:聊天软件或实时监控);排除各类干扰(杀毒软件、测试用或者非正规的芯片驱动、系统漏洞);建议先完全卸载或删除旧版本 再安装可以改善稳定性;Q:显示的帧率和游戏速度不符??A:程序依据硬件的潜力自动跳帧处理,部分游戏超过20FPS...

单片机串口接收函数,在程序中调用时提示错误,不知道错在何处,请大侠...
第 56行 定义错误 提示是 unsigned 检查有没有字符打反了 我是建议 一开头就define一下 define uchar unsigned char define uint unsigned int 这样会比较好点

浪卡子县19329599529: 当程序以一种不可捕捉的异常退出时,如何在程序退出时打印一句“proam exit“? -
锁霄安理: if __name__ == "__main__": try: main() ...基本就是这样了……

浪卡子县19329599529: 怎样终止无法正常终止的进程 -
锁霄安理: 任务管理器无法结束进程解决方法:1 单击开始菜单,在搜索框内输入“CMD”2 按回车,打开命令提示符窗口3 在命令提示符窗口中输入:tasklist4 按回车键,就会显示出当前运行的程序5 输入“Taskkill/im 进程名.后缀名 /f”6 按回车键,就会提示“成功:已中止进程”7 这样就可以结束任务管理器无法结束的进程

浪卡子县19329599529: c++程序异常退出怎么打日志 -
锁霄安理: 在stdlib.h中 有个atexit()函数,用来注册一个函数 在程序结束时,被注册的函数会被调用

浪卡子县19329599529: 怎么确保进程异常退出时, 释放掉信号量 -
锁霄安理: 我觉得你可以开辟一个线程监视A线程,如果A退出了,就释放信号量!

浪卡子县19329599529: C语言中什么什么叫做程序异常退出? -
锁霄安理: 就是程序退出后的返回值不同. 如你所说,一般,返回0表示正常退出,返回非0值表示异常退出.如果这是一个独立的程序,那么返回值是没多大作用. 但通常一些程序是被其他程序所调用的,这时返回值就有用了,调用该程序的主程序就能得知该程序执行成功与否,进而作相应处理.

浪卡子县19329599529: android activity 怎么抛异常退出 -
锁霄安理: 在Android中,Activity有个栈,一个Activity结束掉,会回到上一个Activity,并不是退出应用程序.Android中,退出应用程序的方式:1.通过pid int pid = android.os.Process.myPid(); //获取当前应用程序的PID android.os.Process.killProcess...

浪卡子县19329599529: 怎么判断一个进程是否是错误退出
锁霄安理: 可以通过进程退出码判断 DWORD dwRet = WaitForSingleObject(hHandle,INFINITE);//hHandle为进程句柄HANDLE if (WAIT_OBJECT_0 == dwRet){DWORD dwExitCode = 1; if (GetExitCodeProcess(hHandle, &dwExitCode)){通过进程退出的返...

浪卡子县19329599529: 程序出现异常退出怎么解决 -
锁霄安理: 重启按住F8键,出现开机菜单时,选择最后一次正确配置,回车试.不行的话,你再选一下安全模式,进入杀毒、360卫士扫描,处理完,重启试试.如不行重装系统.

浪卡子县19329599529: 线程里发生异常,如何处理 -
锁霄安理: 程序退出的方法:this.Close(); 只是关闭当前窗口,若不是主窗体的话,是无法退出程序的,另外若有托管线程(非主线程),也无法干净地退出;Application.Exit(); 强制所有消息中止,退出所有的窗体,但是若有托管线程(非主线程)

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网