AFL源码分析之afl-as.c
昨天阅读了afl-gcc的源码,可以看出来afl-gcc主要用途是搜索as所在的位置,然后加上必要的参数参数,在调用gcc进行实际的编译
今天又继续阅读了afl-as.c的源码,这部分主要是对于fuzz插桩的编写,阅读完后不清楚可以看一下sakura师傅的博客,有着对源码的理解,也非常感谢hollk师傅的源码注解,降低了阅读的难度
(22条消息) AFL源码分析之afl-as.c详细注释_hollk’s blog-CSDN博客_afl源码
sakuraのAFL源码全注释 | Sakuraのblog (eternalsakura13.com)
1 /* 2 Copyright 2013 Google LLC All rights reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at: 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 /* 18 american fuzzy lop - wrapper for GNU as 19 --------------------------------------- 20 21 Written and maintained by Michal Zalewski <lcamtuf@google.com> 22 23 The sole purpose of this wrapper is to preprocess assembly files generated 24 by GCC / clang and inject the instrumentation bits included from afl-as.h. It 25 is automatically invoked by the toolchain when compiling programs using 26 afl-gcc / afl-clang. 27 28 Note that it's an explicit non-goal to instrument hand-written assembly, 29 be it in separate .s files or in __asm__ blocks. The only aspiration this 30 utility has right now is to be able to skip them gracefully and allow the 31 compilation process to continue. 32 33 That said, see experimental/clang_asm_normalize/ for a solution that may 34 allow clang users to make things work even with hand-crafted assembly. Just 35 note that there is no equivalent for GCC. 36 37 */ 38 39 #define AFL_MAIN 40 41 #include "config.h" 42 #include "types.h" 43 #include "debug.h" 44 #include "alloc-inl.h" 45 46 #include "afl-as.h" 47 48 #include <stdio.h> 49 #include <unistd.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <time.h> 53 #include <ctype.h> 54 #include <fcntl.h> 55 56 #include <sys/wait.h> 57 #include <sys/time.h> 58 59 static u8** as_params; /* Parameters passed to the real 'as' 传递给“as”的参数 */ 60 61 static u8* input_file; /* Originally specified input file 输入文件 */ 62 static u8* modified_file; /* Instrumented file for the real 'as' “as”进行插桩处理的文件 */ 63 64 static u8 be_quiet, /* Quiet mode (no stderr output) 静默模式(没有标准输出) */ 65 clang_mode, /* Running in clang mode? 是否运行在clang模式 */ 66 pass_thru, /* Just pass data through? 只通过数据 */ 67 just_version, /* Just show version? 只显示版本 */ 68 sanitizer; /* Using ASAN / MSAN 是否使用ASAN/MSAN */ 69 70 static u32 inst_ratio = 100, /* Instrumentation probability (%) 插桩覆盖率 */ 71 as_par_cnt = 1; /* Number of params to 'as' 传递给“as”的参数数量初始值 */ 72 73 /* If we don't find --32 or --64 in the command line, default to 74 instrumentation for whichever mode we were compiled with. This is not 75 perfect, but should do the trick for almost all use cases.如果输入命令中没有“--32”或“--64”,则默认检测编译时使用的模式 */ 76 77 #ifdef WORD_SIZE_64 78 79 static u8 use_64bit = 1; //64位的标志 80 81 #else 82 83 static u8 use_64bit = 0; //32位的标志 84 85 #ifdef __APPLE__ //如果是苹国平台 86 # error "Sorry, 32-bit Apple platforms are not supported." //提示苹果不支持32位 87 #endif /* __APPLE__ */ 88 89 #endif /* ^WORD_SIZE_64 */ 90 91 92 /* Examine and modify parameters to pass to 'as'. Note that the file name 93 is always the last parameter passed by GCC, so we exploit this property 94 to keep the code simple. 检查并修改要传递给'as'的参数。注意,文件名总是GCC传递的最后一个参数,因此我们利用这个属性来保持代码简单*/ 95 96 static void edit_params(int argc, char** argv) { 97 98 u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); //获取环境变量TMPDIR和AFL_AS 99 u32 i; 100 101 #ifdef __APPLE__ // 如果 102 103 u8 use_clang_as = 0; 104 105 /* On MacOS X, the Xcode cctool 'as' driver is a bit stale and does not work 106 with the code generated by newer versions of clang that are hand-built 107 by the user. See the thread here: http://goo.gl/HBWDtn. 108 109 To work around this, when using clang and running without AFL_AS 110 specified, we will actually call 'clang -c' instead of 'as -q' to 111 compile the assembly file. 112 113 The tools aren't cmdline-compatible, but at least for now, we can 114 seemingly get away with this by making only very minor tweaks. Thanks 115 to Nico Weber for the idea. */ 116 117 if (clang_mode && !afl_as) { //如果使用clang模式且没有获取到afl_as变量就进入该分支 118 119 use_clang_as = 1; 120 121 afl_as = getenv("AFL_CC"); //获取环境变量AFL_CC 122 if (!afl_as) afl_as = getenv("AFL_CXX"); //如果没有获取到上面的变量就获取AFL_CXX或者赋值clang字符串 123 if (!afl_as) afl_as = "clang"; 124 125 } 126 127 #endif /* __APPLE__ */ 128 129 /* Although this is not documented, GCC also uses TEMP and TMP when TMPDIR 130 is not set. We need to check these non-standard variables to properly 131 handle the pass_thru logic later on. */ 132 133 if (!tmp_dir) tmp_dir = getenv("TEMP"); //如果没有获取到TMPDIR就赋值下面三个中的一种 134 if (!tmp_dir) tmp_dir = getenv("TMP"); 135 if (!tmp_dir) tmp_dir = "/tmp"; 136 137 as_params = ck_alloc((argc + 32) * sizeof(u8*)); 为as_oarams开辟一段空间 138 139 as_params[0] = afl_as ? afl_as : (u8*)"as"; //将上面的afl_as赋值给as_params,如果没有获取到就将字符串as赋值给as_params 140 141 as_params[argc] = 0; //设置最后一个参数为0 142 143 for (i = 1; i < argc - 1; i++) { //从第一个参数便利到最后一个参数 144 145 if (!strcmp(argv[i], "--64")) use_64bit = 1; //如果遍历到--64字符串就将use_64bit设置为1 146 else if (!strcmp(argv[i], "--32")) use_64bit = 0; //如果便利到--32字符串就将use_64bit设置为零 147 148 #ifdef __APPLE__ //如果是苹果平台 149 150 /* The Apple case is a bit different... */ 151 152 if (!strcmp(argv[i], "-arch") && i + 1 < argc) { //如果遍历到了-arch参数 153 154 if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1; //如果是arch x86_64 则设置use_64bint为1 155 else if (!strcmp(argv[i + 1], "i386")) //如果是-arch i386 则报错 156 FATAL("Sorry, 32-bit Apple platforms are not supported."); 157 158 } 159 160 /* Strip options that set the preference for a particular upstream 161 assembler in Xcode. */ 162 163 if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q"))) //如果是clang模式并且参数是-q或者-Q则跳出循环 164 continue; 165 166 #endif /* __APPLE__ */ 167 168 as_params[as_par_cnt++] = argv[i]; 169 170 } 171 172 #ifdef __APPLE__ 173 174 /* When calling clang as the upstream assembler, append -c -x assembler 175 and hope for the best. */ 176 177 if (use_clang_as) { 178 179 as_params[as_par_cnt++] = "-c"; 180 as_params[as_par_cnt++] = "-x"; 181 as_params[as_par_cnt++] = "assembler"; //如果使用的是clang模式则在as_params追加上述三个参数 182 183 } 184 185 #endif /* __APPLE__ */ 186 187 input_file = argv[argc - 1]; //将argv最后一个参数赋值给input_file 188 189 if (input_file[0] == '-') { //如果input_file 的参数是- 190 191 if (!strcmp(input_file + 1, "-version")) { //如果是-version 192 just_version = 1; 193 modified_file = input_file; //则将just_version设置为1然后modified_file设置为-version 194 goto wrap_things_up; //跳转到参数组合尾部尾部 195 } 196 197 if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)"); //如果不是-version则抛出异常 198 else input_file = NULL; 199 200 } else { 201 202 /* Check if this looks like a standard invocation as a part of an attempt 203 to compile a program, rather than using gcc on an ad-hoc .s file in 204 a format we may not understand. This works around an issue compiling 205 NSS. */ 206 207 if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && //如果首字母不是- 判断第9个和第五个字节分把是不是/var/tmp和/tmp 如果都不是则设置pass_thru为1 208 strncmp(input_file, "/var/tmp/", 9) && 209 strncmp(input_file, "/tmp/", 5)) pass_thru = 1; 210 211 } 212 213 modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(), //设置modified_file为类似tmp_dir/.afl-pid-time.s这样的字符串 214 (u32)time(NULL)); 215 216 wrap_things_up: 217 218 as_params[as_par_cnt++] = modified_file;//接收参数为modified最后一个参数 219 as_params[as_par_cnt] = NULL;//参数接收结束 220 221 } 222 223 224 /* Process input file, generate modified_file. Insert instrumentation in all 225 the appropriate places. */ 226 227 static void add_instrumentation(void) {//处理输入文件,生成modified_file,将桩插入所有释放的位置 228 229 static u8 line[MAX_LINE]; 230 231 FILE* inf; 232 FILE* outf; 233 s32 outfd; 234 u32 ins_lines = 0; //插桩计数器 235 236 u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0, 237 skip_intel = 0, skip_app = 0, instrument_next = 0; 238 239 #ifdef __APPLE__ 240 241 u8* colon_pos; 242 243 #endif /* __APPLE__ */ 244 245 if (input_file) { //如果存在输入文件名称 246 247 inf = fopen(input_file, "r"); //尝试获取input_file句柄,将fd赋值给inf 248 if (!inf) PFATAL("Unable to read '%s'", input_file); //如果获取不到则,抛出异常 249 250 } else inf = stdin; //如果不存在文件名则赋值标准输入 251 252 outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600); //以写的方式打开modified_file,如果文件已存在就直接打开,如果没有就创建一个 253 254 if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file); //如果文件没有写权限,就抛出异常 255 256 outf = fdopen(outfd, "w"); //尝试打开 257 258 if (!outf) PFATAL("fdopen() failed"); //打不开就抛出异常 259 260 while (fgets(line, MAX_LINE, inf)) { //循环读取inf指向的文件的每一行到line数组,每行最多MAX_LINE(8192)个字节,含末尾“\0” 261 262 /* In some cases, we want to defer writing the instrumentation trampoline 263 until after all the labels, macros, comments, etc. If we're in this 264 mode, and if the line starts with a tab followed by a character, dump 265 the trampoline now. */ 266 267 if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok && 268 instrument_next && line[0] == '\t' && isalpha(line[1])) { //446行判断是否为defered mode模式(判断instrument_next和instr_ok是否都为1,以及line是否以\t开始,且line[1]是否是字母) 269 270 fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, 271 R(MAP_SIZE)); //插桩 272 273 instrument_next = 0; //instrument_next设置为0 274 ins_lines++; //插桩计数器加1 275 276 } 277 278 /* Output the actual line, call it a day in pass-thru mode. */ 279 280 fputs(line, outf); 281 282 if (pass_thru) continue; 283 284 /* All right, this is where the actual fun begins. For one, we only want to 285 instrument the .text section. So, let's keep track of that in processed 286 files - and let's set instr_ok accordingly.首先,我们只想检测.text部分。让我们在已处理的文件中跟踪它,并相应地设置instr_ok */ 287 288 if (line[0] == '\t' && line[1] == '.') { //判断读入的行是否以\t开头,以及line[1]是否为. 289 290 /* OpenBSD puts jump tables directly inline with the code, which is 291 a bit annoying. They use a specific format of p2align directives 292 around them, so we use that as a signal. OpenBSD将跳转表直接内联到代码中,这有点烦人。它们使用特定格式的p2align指令,所以我们将其用作信号 */ 293 294 if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && //检查是否为p2align指令,如果是则设置skip_next_label为1 295 isdigit(line[10]) && line[11] == '\n') skip_next_label = 1;//instr_ok变量是一个flag,如果为1表示位于.text段,如果为0表示不再.text段 296 297 if (!strncmp(line + 2, "text\n", 5) || 298 !strncmp(line + 2, "section\t.text", 13) || 299 !strncmp(line + 2, "section\t__TEXT,__text", 21) || 300 !strncmp(line + 2, "section __TEXT,__text", 21)) { 301 instr_ok = 1; //匹配"text\n"、"section\t.text"、"section\t__TEXT,__text"、"section __TEXT,__text",如果匹配成功则设置instr_ok为1。跳出本次循环 302 continue; 303 } 304 305 if (!strncmp(line + 2, "section\t", 8) || 306 !strncmp(line + 2, "section ", 8) || 307 !strncmp(line + 2, "bss\n", 4) || 308 !strncmp(line + 2, "data\n", 5)) { 309 instr_ok = 0;//匹配"section\t"、"section "、"bss\n"、"data\n",如果匹配成功说明不是在.text段,设置instr_ok变量为0 310 continue; 311 } 312 313 } 314 315 /* Detect off-flavor assembly (rare, happens in gdb). When this is 316 encountered, we set skip_csect until the opposite directive is 317 seen, and we do not instrument. */ 318 319 if (strstr(line, ".code")) { //判断架构 320 321 if (strstr(line, ".code32")) skip_csect = use_64bit; 322 if (strstr(line, ".code64")) skip_csect = !use_64bit; 323 324 } 325 326 /* Detect syntax changes, as could happen with hand-written assembly. 327 Skip Intel blocks, resume instrumentation when back to AT&T. */ 328 329 if (strstr(line, ".intel_syntax")) skip_intel = 1;//判断是否为Intel汇编语法 330 if (strstr(line, ".att_syntax")) skip_intel = 0;//判断是否为att汇编语法 331 332 /* Detect and skip ad-hoc __asm__ blocks, likewise skipping them. */ 333 334 if (line[0] == '#' || line[1] == '#') {//ad-hoc __asm__块是否跳过 335 336 if (strstr(line, "#APP")) skip_app = 1; 337 if (strstr(line, "#NO_APP")) skip_app = 0; 338 339 } 340 341 /* If we're in the right mood for instrumenting, check for function 342 names or conditional labels. This is a bit messy, but in essence, 343 we want to catch:插桩时终端关注对象 344 345 ^main: - function entry point (always instrumented) 346 ^.L0: - GCC branch labelgcc下的分支标记 347 ^.LBB0_0: - clang branch label (but only in clang mode)clang下的分支标记(仅仅只是在clang模式中) 348 ^\tjnz foo - conditional branches条件跳转分支标记 349 350 ...but not: 351 352 ^# BB#0: - clang comments 353 ^ # BB#0: - ditto 354 ^.Ltmp0: - clang non-branch labels 355 ^.LC0 - GCC non-branch labels 356 ^.LBB0_0: - ditto (when in GCC mode) 357 ^\tjmp foo - non-conditional jumps 358 359 Additionally, clang and GCC on MacOS X follow a different convention 360 with no leading dots on labels, hence the weird maze of #ifdefs 361 later on. 362 363 */ 364 365 if (skip_intel || skip_app || skip_csect || !instr_ok || 366 line[0] == '#' || line[0] == ' ') continue; 367 368 /* Conditional branch instruction (jnz, etc). We append the instrumentation 369 right after the branch (to instrument the not-taken path) and at the 370 branch destination label (handled later on). */ 371 372 if (line[0] == '\t') { 373 374 if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { //对于形如\tj[^m].格式的指令,即条件跳转指令,且R()函数创建的随机数小于插桩密度inst_ratio 375 376 fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, 377 R(MAP_SIZE)); // 判断是否为64位程序,使用fprintf函数将桩插在outf只想的文件的\tj[^ m].跳转指令位置,插入长度为R函数创建的小于MAP_SIZE的随机数 378 379 ins_lines++; //插桩计数器+1,跳出循环进行下一次遍历 380 381 } 382 383 continue; 384 385 } 386 387 /* Label of some sort. This may be a branch destination, but we need to 388 tread carefully and account for several different formatting 389 conventions. */ 390 391 #ifdef __APPLE__ 392 393 /* Apple: L<whatever><digit>: */ 394 395 if ((colon_pos = strstr(line, ":"))) { 396 397 if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { 398 399 #else 400 401 /* Everybody else: .L<whatever>: */ 402 403 if (strstr(line, ":")) { //检查line中是否存在“:” 404 405 if (line[0] == '.') { //检查是否以“.”开始 406 407 #endif /* __APPLE__ */ 408 409 /* .L0: or LBB0_0: style jump destination */ 410 411 #ifdef __APPLE__ 412 413 /* Apple: L<num> / LBB<num> */ 414 415 if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3)))//检查line[1]是否为数字,或者在clang模式下从line开始的三个字节是否为LBB,并且随机数小于插桩密度 416 && R(100) < )inst_ratio { 417 418 #else 419 420 /* Apple: .L<num> / .LBB<num> */ 421 422 if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) 423 && R(100) < inst_ratio) { //检查line[2]是否为数字,或者在clang模式下从line[1]开始的三个字节是否为LBB,并且随机数小于插桩密度 424 425 #endif /* __APPLE__ */ 426 427 /* An optimization is possible here by adding the code only if the 428 label is mentioned in the code in contexts other than call / jmp. 429 That said, this complicates the code by requiring two-pass 430 processing (messy with stdin), and results in a speed gain 431 typically under 10%, because compilers are generally pretty good 432 about not generating spurious intra-function jumps. 433 434 We use deferred output chiefly to avoid disrupting 435 .Lfunc_begin0-style exception handling calculations (a problem on 436 MacOS X). */ 437 438 if (!skip_next_label) instrument_next = 1; else skip_next_label = 0; //检查skip_next_label是否设置,是就将instrument_next设置为1 否就将skip_next_label设置为0 439 440 } 441 442 } else { 443 444 /* Function label (always instrumented, deferred mode). */ 445 446 instrument_next = 1; //否则代表这是一个function label,插桩^func,设置instrument_next为1(defered mode) 447 448 } 449 450 } 451 452 } 453 454 if (ins_lines)//如果插桩计数器不为0 455 fputs(use_64bit ? main_payload_64 : main_payload_32, outf); //向outf中写入main_payload_64或main_payload_32 456 457 if (input_file) fclose(inf); //关闭文件 458 fclose(outf); //关闭文件 459 460 if (!be_quiet) { //如果使用的不是静默模式 461 462 if (!ins_lines) WARNF("No instrumentation targets found%s.", //如果插桩计数器为空,抛异常 463 pass_thru ? " (pass-thru mode)" : ""); 464 else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).", //插桩成功输出 465 ins_lines, use_64bit ? "64" : "32", 466 getenv("AFL_HARDEN") ? "hardened" : 467 (sanitizer ? "ASAN/MSAN" : "non-hardened"), 468 inst_ratio); 469 470 } 471 472 } 473 474 475 /* Main entry point 主函数*/ 476 477 int main(int argc, char** argv) { 478 479 s32 pid; 480 u32 rand_seed; 481 int status; 482 u8* inst_ratio_str = getenv("AFL_INST_RATIO");//获取环境变量AFL_INST_RATIO(该环境变量主要控制检测每个分支的概率,取值为0到100%,设置为0时值检测函数入口的跳转,而不会检测函数分支的跳转) 483 484 struct timeval tv; 485 struct timezone tz; 486 487 clang_mode = !!getenv(CLANG_ENV_VAR); 488 489 if (isatty(2) && !getenv("AFL_QUIET")) { 490 491 SAYF(cCYA "afl-as " cBRI VERSION cRST " by <lcamtuf@google.com>\n"); 492 493 } else be_quiet = 1; 494 495 if (argc < 2) { 496 497 SAYF("\n" 498 "This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n" 499 "executed by the toolchain whenever using afl-gcc or afl-clang. You probably\n" 500 "don't want to run this program directly.\n\n" 501 502 "Rarely, when dealing with extremely complex projects, it may be advisable to\n" 503 "set AFL_INST_RATIO to a value less than 100 in order to reduce the odds of\n" 504 "instrumenting every discovered branch.\n\n"); 505 506 exit(1); 507 508 } 509 510 gettimeofday(&tv, &tz);//获取当前精确时间 511 512 rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid(); //通过当前时间与进程pid进行亦或处理 513 514 srandom(rand_seed);//获得随机化种子 515 516 edit_params(argc, argv);//检查并修改参数以传递给“as”。文件名是以GCC传递的最后一个参数决定的。此函数主要设置变量as_params的值,以及use_64bit/modified_file的值 517 518 if (inst_ratio_str) {//如果获取到"AFL_INST_RATIO"环境变量则进入分支 519 520 if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) // 如果没有将覆盖率写入inst_ratio变量或者inst_ratio中的值超过100的话,则进入分支抛出异常 521 FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); 522 523 } 524 525 if (getenv(AS_LOOP_ENV_VAR))//如果获取到"__AFL_AS_LOOPCHECK"环境变量值,则进入分支 526 FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");//抛出异常 527 528 setenv(AS_LOOP_ENV_VAR, "1", 1);//设置"__AFL_AS_LOOPCHECK"环境变量为1 529 530 /* When compiling with ASAN, we don't have a particularly elegant way to skip 531 ASAN-specific branches. But we can probabilistically compensate for 532 that... */ 533 534 if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { //获取"AFL_USE_ASAN"或"AFL_USE_MSAN"环境变量,如果其中有一个值为1则进入分支 535 sanitizer = 1;//sanitizer设置为1 536 inst_ratio /= 3;//inst_ratio除以3 537 } 538 539 if (!just_version) add_instrumentation();//如果不是只查询version,那么就会进入add_instrumentastion()函数,该函数主要处理输入文件,生成modified_file,将桩插入释放的位置 540 541 if (!(pid = fork())) { //调用fork函数创建一个子进程。在执行execvp函数执行是 542 543 execvp(as_params[0], (char**)as_params);//执行命令和参数 544 FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);//失败了就抛出异常 545 546 } 547 548 if (pid < 0) PFATAL("fork() failed");//等待子进程结束 549 550 if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed"); //读取环境变量"AFL_KEEP_ASSEMBLY"失败,则unlink掉modified_file 551 552 if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file); 553 //设置该环境变量主要是为了防止afl-as删掉插桩后的汇编文件,设置为1会保留插桩后的汇编文件 554 exit(WEXITSTATUS(status)); 555 556 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)