• 博主:易飞扬
  • 原文链接 : http://www.yifeiyang.net/iphone-development-skills-of-debugging-articles-3-crash-after-debugging-skills-program/
  • 转载请保留上面文字。
    模拟器上显示堆栈信息在iPhone上输出日志iPhone应用程序的CrashReporter机能

    CrashLog的位置.dSYM文件解决符号问题
    用StackTrace取得崩溃时的日志
    异常处理机制处理signal总结

    iPhone开发技巧之调试篇(3)— 程序Crash后的调试技巧

    当我们的程序突然死掉了,Xcode突然送出一段 "message sent to deallocated instance" 的错误,我们该怎样定位我们的程序bug呢?

    又或者我们已经通过AdHoc发布了我们的β版程序,更甚至于我们的程序已经发布到了app store上;而当我们的程序突然在测试人员,或者是最终用户那里突然当掉,是否能收集到这样的日志信息,供我们解析bug呢?

    下面的文章中我将逐步深入地说明这些技巧

    模拟器上显示堆栈信息

    当我们在模拟器上调试时,可能经常遇到下面的内存访问错误:

    1
    2011-01-17 20:21:11.41 App[26067:207] *** -[Testedit retain]: message sent to deallocated instance 0x12e4b0

    首先,我们为了定位问题,需要Xcode帮我们显示栈信息,可以通过Scode中执行文件的属性来设置。如下图所示,选中 MallocStackLogging 选项。该选项只能在模拟器上有效,并且如果你改变了iOS的版本后也需要再次设定该选项。

    这之后,你就可以在终端输入 info malloc-history 命令,如下所示;

    1
    (gdb) info malloc-history 0x12e4b0

    之后得到如下的堆栈信息,从此分析具体的问题所在。

    除此之外,也可以使用下面的命令;

    1
    (gdb) shell malloc_history {pid/partial-process-name} {address}

    例如下图所示;

    另外,内存使用时“EXC_BAD_ACCESS”的错误信息也是经常遇到的,这时我们只要将上面执行文件属性中的 NSZombieEnabled 选上,也能定位该问题。

    最后,这些设置信息都是可以在运行期确认的,如下;

    1
    NSLog(@"NSZombieEnabled: %s", getenv("NSZombieEnabled"));

    在iPhone上输出日志

    如果不是在模拟器上,又或者我们的设备没有连接到PC上,那么如何调试我们的程序呢?假如我们通过AdHoc发布了程序,希望随时得到测试人员的反馈,可以利用下面的方法,将标准出力(stderr)信息记录到文件中,然后通过邮件新式发给开发者。

    1. 设置一个开关,用来清空日志文件内容,并切换输出位置;

    1
    2
    3
    4
    5
    6
    7
    8
    - (BOOL)deleteLogFile {[self finishLog];BOOL success = [[NSFileManager defaultManager] removeItemAtPath:[self loggingPath] error:nil];[self startLog];return success;}

    当我们调用上面的deleteLogFile后,就会清空之前的日志,并设置新的输出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    - (void)finishLog {fflush(stderr);dup2(dup(STDERR_FILENO), STDERR_FILENO);close(dup(STDERR_FILENO));}- (NSString*)loggingPath {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *documentsDirectory = [paths objectAtIndex:0];NSString *logPath = [documentsDirectory stringByAppendingPathComponent:@"console.log"];return logPath;}- (void)startLog {freopen([[self loggingPath] cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
    }

    2. 当日志取得之后,可以通过下面的方式发送邮件给开发者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - (void)sendLogByMail {MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];picker.mailComposeDelegate = self;[picker setSubject:[NSString stringWithFormat:@"%@ - Log", [self appName]]];NSString *message = [NSString stringWithContentsOfFile:[self loggingPath] encoding:NSUTF8StringEncoding error:nil];[picker setMessageBody:message isHTML:NO];[self.navigationController presentModalViewController:picker animated:YES];[picker release];}

    iPhone应用程序的CrashReporter机能

    苹果在固件2.0发布的时候,其中一项特性是向iPhone开发者通过邮件发送错误报告,以便开发人员更好的了解自己的软件运行状况。不过不少开发者报告此服务有时无法获取到~/Library/Logs/CrashReporter/MobileDevice directory的错误信息。

    现在苹果提供了一种更简单的方法,使iPhone开发者可以通过iTunes更容易的查看崩溃报告。具体方法使进入iTunesConnect(在进入之前确定你有iPhone开发者帐号),点击管理你应用程序,之后就可以看到用户崩溃日志了。

    这里我介绍一下从设备中取出CrashLog,并解析的方法。

    CrashLog的位置

    程序Crash之后,将设备与PC中的iTunes连接,设备中的CrashLog文件也将一并同步到PC中。其中位置如下;

    1
    2
    3
    4
    5
    6
    7
    8
    Mac:
    ~/Library/Logs/CrashReporter/MobileDeviceWindows Vista/7:
    C:\Users\<user_name>\AppData\Roaming\Apple computer\Logs\CrashReporter/MobileDeviceWindows XP:
    C:\Documents and Settings\<user_name>\Application Data\Apple computer\Logs\CrashReporter

    在这些目录下,会有具体设备的目录,其下就是许多*.crash的文件。

    比如程序TestEditor在iPhone1设备上的crashLog如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    ~Library/Logs/CrashReporter/MobileDevice/iPhone1/TestEditor_2010-09-23-454678_iPhone1.crashIncident Identifier: CAF9ED40-2D59-45EA-96B0-52BDA1115E9F
    CrashReporter Key:   30af939d26f6ecc5f0d08653b2aaf47933ad8b8e
    Process:         TestEditor [12506]
    Path:            /var/mobile/Applications/60ACEDBC-600E-42AF-9252-42E32188A044/TestEditor.app/TestEditor
    Identifier:      TestEditor
    Version:         ??? (???)
    Code Type:       ARM (Native)
    Parent Process:  launchd [1]Date/Time:       2010-09-23 11:25:56.357 +0900
    OS Version:      iPhone OS 3.1.3 (7E18)
    Report Version:  104Exception Type:  EXC_BAD_ACCESS (SIGBUS)
    Exception Codes: KERN_PROTECTION_FAILURE at 0x00000059
    Crashed Thread:  0Thread 0 Crashed:
    0   UIKit                           0x332b98d8 0x331b2000 + 1079512
    1   UIKit                           0x3321d1a8 0x331b2000 + 438696
    2   UIKit                           0x3321d028 0x331b2000 + 438312
    3   UIKit                           0x332b9628 0x331b2000 + 1078824
    4   UIKit                           0x33209d70 0x331b2000 + 359792
    5   UIKit                           0x33209c08 0x331b2000 + 359432
    6   QuartzCore                      0x324cc05c 0x324ae000 + 122972
    7   QuartzCore                      0x324cbe64 0x324ae000 + 122468
    8   CoreFoundation                  0x3244f4bc 0x323f8000 + 357564
    9   CoreFoundation                  0x3244ec18 0x323f8000 + 355352
    10  GraphicsServices                0x342e91c0 0x342e5000 + 16832
    11  UIKit                           0x331b5c28 0x331b2000 + 15400
    12  UIKit                           0x331b4228 0x331b2000 + 8744
    13  TestEditor                      0x00002c3a 0x1000 + 7226
    14  TestEditor                      0x00002c04 0x1000 + 7172
    ... (以下略)

    虽然我们看到了出为题时的堆栈信息,但是因为没有符号信息,仍然不知道到底哪里出问题了...

    .dSYM文件

    编译调试相关的符号信息都被包含在编译时的 xxxx.app.dSYM 文件当中,所以我们在发布程序前将它们保存起来,调试Crash问题的时候会很有用。

    首先,我们来找到该文件。

    用Xcode编译的程序,在其编译目录下都会生成 [程序名].app.dSMY 文件,比如 Xcode 4 的编译目录缺省的是

    1
    2
    3
    4
    5
    ~Library/Developer/Xcode/DerivedData# 在改目录下搜寻编译后的.dSMY文件
    $ cd ~/Library/Developer/Xcode/DerivedData
    $ find . -name '*.dSYM'

    另外,我们也可以通过 Xcode的Preferences... -> Locations -> Locations 的Derived Data来确认该目录的位置。

    上面例子中的程序,我们就找到了其位置是
    1
    ~/Library/Developer/Xcode/DerivedData/TestEditor-aahmlrjpobenlsdvhjppcfqhogru/ArchiveIntermediates/TestEditor/BuildProductsPath/Release-iphoneos/TestEditor.app.dSYM

    ※ 大家每次像App Store发布自己程序的时候都记着保存该文件哦,要不然出现Crash的时候,就无从下手了。

    解决符号问题

    接下来,我们再来介绍一下使用.dSYM文件来恢复程序符号的方法。

    首先,使用一个Xcode提供的叫做 symbolicatecrash 的小工具,它可以实现我们在CrashLog中添加符号信息的机能。该文件位于下面的位置,为方便起见,可以把它拷贝到系统默认路径下。

    1
    2
    3
    /Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash$ sudo cp /Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash /usr/local/bin
    使用下面的命令,可以在终端输出有符号信息的CrashLog
    1
    2
    3
    $ symbolicatecrash [CrashLog file] [dSYM file]$ symbolicatecrash TestEditor_2010-09-23-454678_iPhone1.crash TestEditor.app.dSYM
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Thread 0 Crashed:
    0   UIKit                           0x332b98d8 -[UIWindowController transitionViewDidComplete:fromView:toView:] + 668
    1   UIKit                           0x3321d1a8 -[UITransitionView notifyDidCompleteTransition:] + 160
    2   UIKit                           0x3321d028 -[UITransitionView _didCompleteTransition:] + 704
    3   UIKit                           0x332b9628 -[UITransitionView _transitionDidStop:finished:] + 44
    4   UIKit                           0x33209d70 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 284
    5   UIKit                           0x33209c08 -[UIViewAnimationState animationDidStop:finished:] + 60
    6   QuartzCore                      0x324cc05c _ZL23run_animation_callbacksdPv + 440
    7   QuartzCore                      0x324cbe64 _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv + 156
    8   CoreFoundation                  0x3244f4bc CFRunLoopRunSpecific + 2192
    9   CoreFoundation                  0x3244ec18 CFRunLoopRunInMode + 44
    10  GraphicsServices                0x342e91c0 GSEventRunModal + 188
    11  UIKit                           0x331b5c28 -[UIApplication _run] + 552
    12  UIKit                           0x331b4228 UIApplicationMain + 960
    13  TestEditor                      0x00002c3a main (main.m:14)
    14  TestEditor                      0x00002c04 0x1000 + 7172

    由此,我们可以具体定位程序中出问题的地方。

    用StackTrace取得崩溃时的日志

    异常处理机制

    任何语言都有异常的处理机制,Objective-C也不例外。与C++/Java类似的语法,它也提供@try, @catch, @throw, @finally关键字。使用方法如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @try {
    ...   }
    @catch (CustomException *ce) {
    ...   }
    @catch (NSException *ne) {
    // Perform processing necessary at this level.
    ...  }
    @catch (id ue) {
    ...   }
    @finally {
    // Perform processing necessary whether an exception occurred or not.
    ...   }

    同时对于系统Crash而引起的程序异常退出,可以通过UncaughtExceptionHandler机制捕获;也就是说在程序中catch以外的内容,被系统自带的错误处理而捕获。我们要做的就是用自定义的函数替代该ExceptionHandler即可。

    这里主要有两个函数
    • NSGetUncaughtExceptionHandler() 得到现在系统自带处理Handler;得到它后,如果程序正常退出时用来回复系统原先设置
    • NSSetUncaughtExceptionHandler() 红色设置自定义的函数
    简单的使用例子如下所示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    void MyUncaughtExceptionHandler(NSException *exception)
    {printf("uncaught %s\n", [[exception name] cString]);// ~
    // 显示当前堆栈内容
        NSArray *callStackArray = [exception callStackReturnAddresses];int frameCount = [callStackArray count];void *backtraceFrames[frameCount];for (int i=0; i<frameCount; i++) {backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue];}
    }int main()
    {// ~
        NSUncaughtExceptionHandler *ueh = NSGetUncaughtExceptionHandler();NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);// ~
    }- (void)exit_processing:(NSNotification *)notification {NSSetUncaughtExceptionHandler(ueh);
    }- (void)viewDidLoad {// 这里重载程序正常退出时UIApplicationWillTerminateNotification接口
        UIApplication *app = [UIApplication sharedApplication];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(exit_processing:)name:UIApplicationWillTerminateNotificationobject:app]
    }

    处理signal

    使用Objective-C的异常处理是不能得到signal的,如果要处理它,我们还要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。该函数中我们可以输出栈信息,版本信息等其他一切我们所想要的。

    例子代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <signal.h>void stacktrace(int sig, siginfo_t *info, void *context)
    {[mstr appendString:@"Stack:\n"];void* callstack[128];int i, frames = backtrace(callstack, 128);char** strs = backtrace_symbols(callstack, frames);for (i = 0; i <; frames; ++i) {[mstr appendFormat:@"%s\n", strs[i]];}
    }int main(int argc, char *argv[])
    {struct sigaction mySigAction;mySigAction.sa_sigaction = stacktrace;mySigAction.sa_flags = SA_SIGINFO;sigemptyset(&mySigAction.sa_mask);sigaction(SIGQUIT, &mySigAction, NULL);sigaction(SIGILL , &mySigAction, NULL);sigaction(SIGTRAP, &mySigAction, NULL);sigaction(SIGABRT, &mySigAction, NULL);sigaction(SIGEMT , &mySigAction, NULL);sigaction(SIGFPE , &mySigAction, NULL);sigaction(SIGBUS , &mySigAction, NULL);sigaction(SIGSEGV, &mySigAction, NULL);sigaction(SIGSYS , &mySigAction, NULL);sigaction(SIGPIPE, &mySigAction, NULL);sigaction(SIGALRM, &mySigAction, NULL);sigaction(SIGXCPU, &mySigAction, NULL);sigaction(SIGXFSZ, &mySigAction, NULL);NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];int retVal = UIApplicationMain(argc, argv, nil, nil);[pool release];return (retVal);
    }

    总结

    综上所述,我们可以看到用StackTrace取得崩溃时日志的手顺如下
    • 用NSGetUncaughtExceptionHandler()取得当前系统异常处理Handler
    • 用NSSetUncaughtExceptionHandler()注册自定义异常处理Handler
    • 注册signal处理机制
      • 注册Handler中打印堆栈,版本号等信息
      • 必要的时候将其保存到dump.txt文件
    • 异常程序退出
    • 如果程序不是异常退出,则还原之前系统的异常处理函数句柄
    • 如果下次程序启动,发现有dump.txt的异常文件,启动邮件发送报告机制
    整体的代码框架如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include <signal.h>void stacktrace(int sig, siginfo_t *info, void *context)
    {[mstr appendString:@"Stack:\n"];void* callstack[128];int i, frames = backtrace(callstack, 128);char** strs = backtrace_symbols(callstack, frames);for (i = 0; i <; frames; ++i) {[mstr appendFormat:@"%s\n", strs[i]];}
    }void MyUncaughtExceptionHandler(NSException *exception)
    {printf("uncaught %s\n", [[exception name] cString]);// ~
    // 显示当前堆栈内容
        NSArray *callStackArray = [exception callStackReturnAddresses];int frameCount = [callStackArray count];void *backtraceFrames[frameCount];for (int i=0; i<frameCount; i++) {backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue];}
    }int main(int argc, char *argv[])
    {struct sigaction mySigAction;mySigAction.sa_sigaction = stacktrace;mySigAction.sa_flags = SA_SIGINFO;sigemptyset(&mySigAction.sa_mask);sigaction(SIGQUIT, &mySigAction, NULL);sigaction(SIGILL , &mySigAction, NULL);sigaction(SIGTRAP, &mySigAction, NULL);sigaction(SIGABRT, &mySigAction, NULL);sigaction(SIGEMT , &mySigAction, NULL);sigaction(SIGFPE , &mySigAction, NULL);sigaction(SIGBUS , &mySigAction, NULL);sigaction(SIGSEGV, &mySigAction, NULL);sigaction(SIGSYS , &mySigAction, NULL);sigaction(SIGPIPE, &mySigAction, NULL);sigaction(SIGALRM, &mySigAction, NULL);sigaction(SIGXCPU, &mySigAction, NULL);sigaction(SIGXFSZ, &mySigAction, NULL);// ~
          NSUncaughtExceptionHandler *ueh = NSGetUncaughtExceptionHandler();NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);// ~
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];int retVal = UIApplicationMain(argc, argv, nil, nil);[pool release];return (retVal);
    }- (void)exit_processing:(NSNotification *)notification {NSSetUncaughtExceptionHandler(ueh);
    }- (void)viewDidLoad {// 这里重载程序正常退出时UIApplicationWillTerminateNotification接口
        UIApplication *app = [UIApplication sharedApplication];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(exit_processing:)name:UIApplicationWillTerminateNotificationobject:app]
    }
    输入的CrashLog如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Signal:10
    Stack:
    0 TestEditor 0x0006989d dump + 64
    1 TestEditor 0x00069b4b signalHandler + 46
    2 libSystem.B.dylib 0x31dcd60b _sigtramp + 26
    3 TestEditor 0x000252b9 -[PopClientcreateUnreadMessageWithUIDL:maxMessageCount:] + 76
    4 TestEditor 0x00025b85 -[PopClientgetUnreadIdList:] + 348
    5 TestEditor 0x000454dd -[Connection receiveMessages:] + 688
    6 TestEditor 0x00042db1 -[Connection main] + 188
    7 Foundation 0x305023f9 __NSThread__main__ + 858
    8 libSystem.B.dylib 0x31d6a5a8 _pthread_body + 28
    AppVer:TestEditor 1.2.0
    System:iPhone OS
    OS Ver:3.0
    Model:iPhoneDate:09/06/08 21:25:59JST

    其中从_sigtramp函数下面开始进入我们的程序,即地址0x000252b9开始。其所对应的具体文件名和行号我们能知道吗?

    利用之前介绍的dSYM文件和gdb,我们可以得到这些信息。

    1
    2
    3
    4
    5
    6
    7
    cd $PROJ_PATH$/build/Release-iphoneos/TestEditor.app.dSYM/
    cd Contents/Resources/DWARF
    gdb TestEditor
    gdb>info line *0x000252b9
    Line 333 of "~/IbisMail/Classes/Models/PopClient.m";
    starts at address 0x2a386 <-[PopClient retrieve:]+86> and
    ends at 0x2a390 <-[PopClient retrieve:]+96>

iPhone开发技巧之调试 — 程序Crash后的调试技巧相关推荐

  1. 程序Crash后的调试技巧

    当我们的程序突然死掉了,Xcode突然送出一段 "message sent to deallocated instance" 的错误,我们该怎样定位我们的程序bug呢? 又或者我们 ...

  2. wp8开发笔记之应用程序真机发布调试

    写在前面的话: 相信很多和我一样关注wp8动态以及已经开发了一些wp8应用程序的朋友们都遇到过这样的问题,写的程序在模拟器上进行调试总是显的没什么说服力,想拿真机进行实际测试, 该怎么办才好呢? 进行 ...

  3. iPhone 开发基础教程

    Phone 开发基础教程之 在开始编写iPhone软件之前,需要做一些准备工作.对于初学者,需要一台运行Leopard(OS X 10.5.3或更高版本)的基于Intel的Macintosh计算机.2 ...

  4. 开发 | 四大微信小程序开发工具测评

    一.微信小程序官方开发工具 注意,它只是个工具,而不是一个IDE.官方工具中的代码编辑功能,就是将vscode的代码编辑功能嵌入到工具中,不足以支撑开发. 优点 因为是官方工具所以有这其它第三方工具有 ...

  5. Win8+VmWare9实现iphone开发环境

    都说iphone开发是富帅程序员的职业,像我等屌丝买不起imac   air 等苹果的的,只好在win/linux+eclipse上面搞搞山寨之王的android.前几天在远景论坛看到一个装黑苹果的帖 ...

  6. PHP程序Laravel框架的优化技巧

    Laravel是一套简洁.优雅的php Web开发框架(PHP Web Framework).它可以让你从杂乱的代码中解脱出来,可以帮你构建一个完美的网络app,而且每行代码都简洁.富于表达力.而性能 ...

  7. 都2020年,开发制作微信小程序商城,需要准备什么资料?应该不会不知道吧!

    微信小程序使用越来越广泛,而还未入局的对于微信小程序开发需要什么材料甚至不清楚,其实这些材料很简单.总结就是微信支付.微信小程序注册.小程序代码,下面展开说说. 小程序红利 从腾讯刚发布的财报看,微信 ...

  8. .net程序员的iPhone开发-MonoTouch

      iPhone软件的Native开发除了使用Apple推荐的Objective-C  Cocoa之外,也有其他的一些工具和SDK提供 基于WEB的形式的一些框架在下面这个文章介绍过 各种SmartP ...

  9. net程序员的iPhone开发-MonoTouch

    net程序员的iPhone开发-MonoTouch iPhone软件的Native开发除了使用Apple推荐的Objective-C  Cocoa之外,也有其他的一些工具和SDK提供 基于WEB的形式 ...

  10. ios学习--iphone开发笔记和技巧总结(原址持续更新)

    ios学习--iphone开发笔记和技巧总结(原址持续更新) 分类: ios Object-C2012-04-18 10:16 2716人阅读 评论(1) 收藏 举报 uiviewiphonelist ...

最新文章

  1. ue4cmd怎么调用_[UE4,automation]UE4批渲染cmd篇
  2. Python分析101位《创造营2020》小姐姐,谁才是你心中的颜值担当?
  3. user-agent
  4. wifi卡慢延迟高_健康生活好助手:华为智能体脂秤 WiFi 版 体验评测
  5. select、poll、epoll之间的区别
  6. 深圳南山区法院受理11人集体诉腾讯案
  7. 【opencv】图像处理之伽马变换
  8. 项目管理图书泄露章节-----关于项目内容中的其他
  9. P3047 [USACO12FEB]附近的牛Nearby Cows
  10. linux 版本号 加号,Linux kernel编译生成的版本多一个加号“+”
  11. python入门经典-Python入门经典书籍有哪些?有这三本就够了
  12. 数字图像处理第四版更新内容
  13. python中os关于目录创建和文件移动操作
  14. html的取消和选中,checkbox 选中和取消切换问题
  15. 山东大学项目实训(二十七)—— 微信小程序开发总结,一年时间真的可以改变一个人很多
  16. 反弹球消砖块C语言重构函数封装
  17. 部署harbor服务器(https/http)
  18. 基于RT-Thread+RA6M4的远程开机助手
  19. 两种参考模型:OSI参考模型、TCP/IP模型
  20. Android使用讯飞SDK开发语音识别及合成小Demo

热门文章

  1. Summery of the first homework
  2. js打印时分页,每页都有表头和表尾
  3. redis报错 Error getaddrinfo ENOTFOUND
  4. Java学习手册:大疆创新2020届校园招聘——软件类岗位B卷题一
  5. CRM软件的未来发展趋势
  6. 网易,抖音音乐人认证教程
  7. SV--随机约束(一)
  8. 打开idea注释doc的rendered view模式
  9. 在线微信公众号调查数据分析报告
  10. 电商双十一调查数据分析报告