1 2 #include "libcflat.h" 3 #include "desc.h" 4 #include "processor.h" 5 6 #define smp_id() 0 7 8 #define true 1 9 #define false 0 10 11 static _Bool verbose = false; 12 13 typedef unsigned long pt_element_t; 14 15 #define PAGE_SIZE ((pt_element_t)4096) 16 #define PAGE_MASK (~(PAGE_SIZE-1)) 17 18 #define PT_BASE_ADDR_MASK ((pt_element_t)((((pt_element_t)1 << 40) - 1) & PAGE_MASK)) 19 #define PT_PSE_BASE_ADDR_MASK (PT_BASE_ADDR_MASK & ~(1ull << 21)) 20 21 #define PT_PRESENT_MASK ((pt_element_t)1 << 0) 22 #define PT_WRITABLE_MASK ((pt_element_t)1 << 1) 23 #define PT_USER_MASK ((pt_element_t)1 << 2) 24 #define PT_ACCESSED_MASK ((pt_element_t)1 << 5) 25 #define PT_DIRTY_MASK ((pt_element_t)1 << 6) 26 #define PT_PSE_MASK ((pt_element_t)1 << 7) 27 #define PT_NX_MASK ((pt_element_t)1 << 63) 28 29 #define CR0_WP_MASK (1UL << 16) 30 31 #define PFERR_PRESENT_MASK (1U << 0) 32 #define PFERR_WRITE_MASK (1U << 1) 33 #define PFERR_USER_MASK (1U << 2) 34 #define PFERR_RESERVED_MASK (1U << 3) 35 #define PFERR_FETCH_MASK (1U << 4) 36 37 #define MSR_EFER 0xc0000080 38 #define EFER_NX_MASK (1ull << 11) 39 40 #define PT_INDEX(address, level) \ 41 ((address) >> (12 + ((level)-1) * 9)) & 511 42 43 /* 44 * page table access check tests 45 */ 46 47 enum { 48 AC_PTE_PRESENT, 49 AC_PTE_WRITABLE, 50 AC_PTE_USER, 51 AC_PTE_ACCESSED, 52 AC_PTE_DIRTY, 53 AC_PTE_NX, 54 AC_PTE_BIT51, 55 56 AC_PDE_PRESENT, 57 AC_PDE_WRITABLE, 58 AC_PDE_USER, 59 AC_PDE_ACCESSED, 60 AC_PDE_DIRTY, 61 AC_PDE_PSE, 62 AC_PDE_NX, 63 AC_PDE_BIT51, 64 65 AC_ACCESS_USER, 66 AC_ACCESS_WRITE, 67 AC_ACCESS_FETCH, 68 AC_ACCESS_TWICE, 69 // AC_ACCESS_PTE, 70 71 AC_CPU_EFER_NX, 72 AC_CPU_CR0_WP, 73 74 NR_AC_FLAGS 75 }; 76 77 const char *ac_names[] = { 78 [AC_PTE_PRESENT] = "pte.p", 79 [AC_PTE_ACCESSED] = "pte.a", 80 [AC_PTE_WRITABLE] = "pte.rw", 81 [AC_PTE_USER] = "pte.user", 82 [AC_PTE_DIRTY] = "pte.d", 83 [AC_PTE_NX] = "pte.nx", 84 [AC_PTE_BIT51] = "pte.51", 85 [AC_PDE_PRESENT] = "pde.p", 86 [AC_PDE_ACCESSED] = "pde.a", 87 [AC_PDE_WRITABLE] = "pde.rw", 88 [AC_PDE_USER] = "pde.user", 89 [AC_PDE_DIRTY] = "pde.d", 90 [AC_PDE_PSE] = "pde.pse", 91 [AC_PDE_NX] = "pde.nx", 92 [AC_PDE_BIT51] = "pde.51", 93 [AC_ACCESS_WRITE] = "write", 94 [AC_ACCESS_USER] = "user", 95 [AC_ACCESS_FETCH] = "fetch", 96 [AC_ACCESS_TWICE] = "twice", 97 [AC_CPU_EFER_NX] = "efer.nx", 98 [AC_CPU_CR0_WP] = "cr0.wp", 99 }; 100 101 static inline void *va(pt_element_t phys) 102 { 103 return (void *)phys; 104 } 105 106 typedef struct { 107 pt_element_t pt_pool; 108 unsigned pt_pool_size; 109 unsigned pt_pool_current; 110 } ac_pool_t; 111 112 typedef struct { 113 unsigned flags[NR_AC_FLAGS]; 114 void *virt; 115 pt_element_t phys; 116 pt_element_t *ptep; 117 pt_element_t expected_pte; 118 pt_element_t *pdep; 119 pt_element_t expected_pde; 120 pt_element_t ignore_pde; 121 int expected_fault; 122 unsigned expected_error; 123 } ac_test_t; 124 125 typedef struct { 126 unsigned short limit; 127 unsigned long linear_addr; 128 } __attribute__((packed)) descriptor_table_t; 129 130 131 static void ac_test_show(ac_test_t *at); 132 133 void set_cr0_wp(int wp) 134 { 135 unsigned long cr0 = read_cr0(); 136 137 cr0 &= ~CR0_WP_MASK; 138 if (wp) 139 cr0 |= CR0_WP_MASK; 140 write_cr0(cr0); 141 } 142 143 void set_efer_nx(int nx) 144 { 145 unsigned long long efer; 146 147 efer = rdmsr(MSR_EFER); 148 efer &= ~EFER_NX_MASK; 149 if (nx) 150 efer |= EFER_NX_MASK; 151 wrmsr(MSR_EFER, efer); 152 } 153 154 static void ac_env_int(ac_pool_t *pool) 155 { 156 setup_idt(); 157 158 extern char page_fault, kernel_entry; 159 set_idt_entry(14, &page_fault, 0); 160 set_idt_entry(0x20, &kernel_entry, 3); 161 162 pool->pt_pool = 33 * 1024 * 1024; 163 pool->pt_pool_size = 120 * 1024 * 1024 - pool->pt_pool; 164 pool->pt_pool_current = 0; 165 } 166 167 void ac_test_init(ac_test_t *at, void *virt) 168 { 169 wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_NX_MASK); 170 set_cr0_wp(1); 171 for (int i = 0; i < NR_AC_FLAGS; ++i) 172 at->flags[i] = 0; 173 at->virt = virt; 174 at->phys = 32 * 1024 * 1024; 175 } 176 177 int ac_test_bump_one(ac_test_t *at) 178 { 179 for (int i = 0; i < NR_AC_FLAGS; ++i) 180 if (!at->flags[i]) { 181 at->flags[i] = 1; 182 return 1; 183 } else 184 at->flags[i] = 0; 185 return 0; 186 } 187 188 _Bool ac_test_legal(ac_test_t *at) 189 { 190 if (at->flags[AC_ACCESS_FETCH] && at->flags[AC_ACCESS_WRITE]) 191 return false; 192 return true; 193 } 194 195 int ac_test_bump(ac_test_t *at) 196 { 197 int ret; 198 199 ret = ac_test_bump_one(at); 200 while (ret && !ac_test_legal(at)) 201 ret = ac_test_bump_one(at); 202 return ret; 203 } 204 205 void invlpg(void *addr) 206 { 207 asm volatile ("invlpg (%0)" : : "r"(addr)); 208 } 209 210 pt_element_t ac_test_alloc_pt(ac_pool_t *pool) 211 { 212 pt_element_t ret = pool->pt_pool + pool->pt_pool_current; 213 pool->pt_pool_current += PAGE_SIZE; 214 return ret; 215 } 216 217 _Bool ac_test_enough_room(ac_pool_t *pool) 218 { 219 return pool->pt_pool_current + 4 * PAGE_SIZE <= pool->pt_pool_size; 220 } 221 222 void ac_test_reset_pt_pool(ac_pool_t *pool) 223 { 224 pool->pt_pool_current = 0; 225 } 226 227 void ac_set_expected_status(ac_test_t *at) 228 { 229 int pde_valid, pte_valid; 230 231 invlpg(at->virt); 232 233 if (at->ptep) 234 at->expected_pte = *at->ptep; 235 at->expected_pde = *at->pdep; 236 at->ignore_pde = 0; 237 at->expected_fault = 0; 238 at->expected_error = PFERR_PRESENT_MASK; 239 240 pde_valid = at->flags[AC_PDE_PRESENT] 241 && !at->flags[AC_PDE_BIT51] 242 && !(at->flags[AC_PDE_NX] && !at->flags[AC_CPU_EFER_NX]); 243 pte_valid = pde_valid 244 && at->flags[AC_PTE_PRESENT] 245 && !at->flags[AC_PTE_BIT51] 246 && !(at->flags[AC_PTE_NX] && !at->flags[AC_CPU_EFER_NX]); 247 if (at->flags[AC_ACCESS_TWICE]) { 248 if (pde_valid) { 249 at->expected_pde |= PT_ACCESSED_MASK; 250 if (pte_valid) 251 at->expected_pte |= PT_ACCESSED_MASK; 252 } 253 } 254 255 if (at->flags[AC_ACCESS_USER]) 256 at->expected_error |= PFERR_USER_MASK; 257 258 if (at->flags[AC_ACCESS_WRITE]) 259 at->expected_error |= PFERR_WRITE_MASK; 260 261 if (at->flags[AC_ACCESS_FETCH]) 262 at->expected_error |= PFERR_FETCH_MASK; 263 264 if (!at->flags[AC_PDE_PRESENT]) { 265 at->expected_fault = 1; 266 at->expected_error &= ~PFERR_PRESENT_MASK; 267 } else if (!pde_valid) { 268 at->expected_fault = 1; 269 at->expected_error |= PFERR_RESERVED_MASK; 270 } 271 272 if (at->flags[AC_ACCESS_USER] && !at->flags[AC_PDE_USER]) 273 at->expected_fault = 1; 274 275 if (at->flags[AC_ACCESS_WRITE] 276 && !at->flags[AC_PDE_WRITABLE] 277 && (at->flags[AC_CPU_CR0_WP] || at->flags[AC_ACCESS_USER])) 278 at->expected_fault = 1; 279 280 if (at->flags[AC_ACCESS_FETCH] && at->flags[AC_PDE_NX]) 281 at->expected_fault = 1; 282 283 if (!at->flags[AC_PDE_ACCESSED]) 284 at->ignore_pde = PT_ACCESSED_MASK; 285 286 if (!pde_valid) 287 goto fault; 288 289 if (!at->expected_fault) 290 at->expected_pde |= PT_ACCESSED_MASK; 291 292 if (at->flags[AC_PDE_PSE]) { 293 if (at->flags[AC_ACCESS_WRITE] && !at->expected_fault) 294 at->expected_pde |= PT_DIRTY_MASK; 295 goto no_pte; 296 } 297 298 if (!at->flags[AC_PTE_PRESENT]) { 299 at->expected_fault = 1; 300 at->expected_error &= ~PFERR_PRESENT_MASK; 301 } else if (!pte_valid) { 302 at->expected_fault = 1; 303 at->expected_error |= PFERR_RESERVED_MASK; 304 } 305 306 if (at->flags[AC_ACCESS_USER] && !at->flags[AC_PTE_USER]) 307 at->expected_fault = 1; 308 309 if (at->flags[AC_ACCESS_WRITE] 310 && !at->flags[AC_PTE_WRITABLE] 311 && (at->flags[AC_CPU_CR0_WP] || at->flags[AC_ACCESS_USER])) 312 at->expected_fault = 1; 313 314 if (at->flags[AC_ACCESS_FETCH] && at->flags[AC_PTE_NX]) 315 at->expected_fault = 1; 316 317 if (at->expected_fault) 318 goto fault; 319 320 at->expected_pte |= PT_ACCESSED_MASK; 321 if (at->flags[AC_ACCESS_WRITE]) 322 at->expected_pte |= PT_DIRTY_MASK; 323 324 no_pte: 325 fault: 326 if (!at->expected_fault) 327 at->ignore_pde = 0; 328 if (!at->flags[AC_CPU_EFER_NX]) 329 at->expected_error &= ~PFERR_FETCH_MASK; 330 } 331 332 void __ac_setup_specific_pages(ac_test_t *at, ac_pool_t *pool, u64 pd_page, 333 u64 pt_page) 334 335 { 336 unsigned long root = read_cr3(); 337 338 if (!ac_test_enough_room(pool)) 339 ac_test_reset_pt_pool(pool); 340 341 at->ptep = 0; 342 for (int i = 4; i >= 1 && (i >= 2 || !at->flags[AC_PDE_PSE]); --i) { 343 pt_element_t *vroot = va(root & PT_BASE_ADDR_MASK); 344 unsigned index = PT_INDEX((unsigned long)at->virt, i); 345 pt_element_t pte = 0; 346 switch (i) { 347 case 4: 348 case 3: 349 pte = pd_page ? pd_page : ac_test_alloc_pt(pool); 350 pte |= PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK; 351 break; 352 case 2: 353 if (!at->flags[AC_PDE_PSE]) 354 pte = pt_page ? pt_page : ac_test_alloc_pt(pool); 355 else { 356 pte = at->phys & PT_PSE_BASE_ADDR_MASK; 357 pte |= PT_PSE_MASK; 358 } 359 if (at->flags[AC_PDE_PRESENT]) 360 pte |= PT_PRESENT_MASK; 361 if (at->flags[AC_PDE_WRITABLE]) 362 pte |= PT_WRITABLE_MASK; 363 if (at->flags[AC_PDE_USER]) 364 pte |= PT_USER_MASK; 365 if (at->flags[AC_PDE_ACCESSED]) 366 pte |= PT_ACCESSED_MASK; 367 if (at->flags[AC_PDE_DIRTY]) 368 pte |= PT_DIRTY_MASK; 369 if (at->flags[AC_PDE_NX]) 370 pte |= PT_NX_MASK; 371 if (at->flags[AC_PDE_BIT51]) 372 pte |= 1ull << 51; 373 at->pdep = &vroot[index]; 374 break; 375 case 1: 376 pte = at->phys & PT_BASE_ADDR_MASK; 377 if (at->flags[AC_PTE_PRESENT]) 378 pte |= PT_PRESENT_MASK; 379 if (at->flags[AC_PTE_WRITABLE]) 380 pte |= PT_WRITABLE_MASK; 381 if (at->flags[AC_PTE_USER]) 382 pte |= PT_USER_MASK; 383 if (at->flags[AC_PTE_ACCESSED]) 384 pte |= PT_ACCESSED_MASK; 385 if (at->flags[AC_PTE_DIRTY]) 386 pte |= PT_DIRTY_MASK; 387 if (at->flags[AC_PTE_NX]) 388 pte |= PT_NX_MASK; 389 if (at->flags[AC_PTE_BIT51]) 390 pte |= 1ull << 51; 391 at->ptep = &vroot[index]; 392 break; 393 } 394 vroot[index] = pte; 395 root = vroot[index]; 396 } 397 ac_set_expected_status(at); 398 } 399 400 static void ac_test_setup_pte(ac_test_t *at, ac_pool_t *pool) 401 { 402 __ac_setup_specific_pages(at, pool, 0, 0); 403 } 404 405 static void ac_setup_specific_pages(ac_test_t *at, ac_pool_t *pool, 406 u64 pd_page, u64 pt_page) 407 { 408 return __ac_setup_specific_pages(at, pool, pd_page, pt_page); 409 } 410 411 static void dump_mapping(ac_test_t *at) 412 { 413 unsigned long root = read_cr3(); 414 int i; 415 416 printf("Dump mapping: address: %llx\n", at->virt); 417 for (i = 4; i >= 1 && (i >= 2 || !at->flags[AC_PDE_PSE]); --i) { 418 pt_element_t *vroot = va(root & PT_BASE_ADDR_MASK); 419 unsigned index = PT_INDEX((unsigned long)at->virt, i); 420 pt_element_t pte = vroot[index]; 421 422 printf("------L%d: %llx\n", i, pte); 423 root = vroot[index]; 424 } 425 } 426 427 static void ac_test_check(ac_test_t *at, _Bool *success_ret, _Bool cond, 428 const char *fmt, ...) 429 { 430 va_list ap; 431 char buf[500]; 432 433 if (!*success_ret) { 434 return; 435 } 436 437 if (!cond) { 438 return; 439 } 440 441 *success_ret = false; 442 443 if (!verbose) { 444 ac_test_show(at); 445 } 446 447 va_start(ap, fmt); 448 vsnprintf(buf, sizeof(buf), fmt, ap); 449 va_end(ap); 450 printf("FAIL: %s\n", buf); 451 dump_mapping(at); 452 } 453 454 static int pt_match(pt_element_t pte1, pt_element_t pte2, pt_element_t ignore) 455 { 456 pte1 &= ~ignore; 457 pte2 &= ~ignore; 458 return pte1 == pte2; 459 } 460 461 int ac_test_do_access(ac_test_t *at) 462 { 463 static unsigned unique = 42; 464 int fault = 0; 465 unsigned e; 466 static unsigned char user_stack[4096]; 467 unsigned long rsp; 468 _Bool success = true; 469 470 ++unique; 471 472 *((unsigned char *)at->phys) = 0xc3; /* ret */ 473 474 unsigned r = unique; 475 set_cr0_wp(at->flags[AC_CPU_CR0_WP]); 476 set_efer_nx(at->flags[AC_CPU_EFER_NX]); 477 478 if (at->flags[AC_ACCESS_TWICE]) { 479 asm volatile ( 480 "mov $fixed2, %%rsi \n\t" 481 "mov (%[addr]), %[reg] \n\t" 482 "fixed2:" 483 : [reg]"=r"(r), [fault]"=a"(fault), "=b"(e) 484 : [addr]"r"(at->virt) 485 : "rsi" 486 ); 487 fault = 0; 488 } 489 490 asm volatile ("mov $fixed1, %%rsi \n\t" 491 "mov %%rsp, %%rdx \n\t" 492 "cmp $0, %[user] \n\t" 493 "jz do_access \n\t" 494 "push %%rax; mov %[user_ds], %%ax; mov %%ax, %%ds; pop %%rax \n\t" 495 "pushq %[user_ds] \n\t" 496 "pushq %[user_stack_top] \n\t" 497 "pushfq \n\t" 498 "pushq %[user_cs] \n\t" 499 "pushq $do_access \n\t" 500 "iretq \n" 501 "do_access: \n\t" 502 "cmp $0, %[fetch] \n\t" 503 "jnz 2f \n\t" 504 "cmp $0, %[write] \n\t" 505 "jnz 1f \n\t" 506 "mov (%[addr]), %[reg] \n\t" 507 "jmp done \n\t" 508 "1: mov %[reg], (%[addr]) \n\t" 509 "jmp done \n\t" 510 "2: call *%[addr] \n\t" 511 "done: \n" 512 "fixed1: \n" 513 "int %[kernel_entry_vector] \n\t" 514 "back_to_kernel:" 515 : [reg]"+r"(r), "+a"(fault), "=b"(e), "=&d"(rsp) 516 : [addr]"r"(at->virt), 517 [write]"r"(at->flags[AC_ACCESS_WRITE]), 518 [user]"r"(at->flags[AC_ACCESS_USER]), 519 [fetch]"r"(at->flags[AC_ACCESS_FETCH]), 520 [user_ds]"i"(32+3), 521 [user_cs]"i"(24+3), 522 [user_stack_top]"r"(user_stack + sizeof user_stack), 523 [kernel_entry_vector]"i"(0x20) 524 : "rsi"); 525 526 asm volatile (".section .text.pf \n\t" 527 "page_fault: \n\t" 528 "pop %rbx \n\t" 529 "mov %rsi, (%rsp) \n\t" 530 "movl $1, %eax \n\t" 531 "iretq \n\t" 532 ".section .text"); 533 534 asm volatile (".section .text.entry \n\t" 535 "kernel_entry: \n\t" 536 "mov %rdx, %rsp \n\t" 537 "jmp back_to_kernel \n\t" 538 ".section .text"); 539 540 ac_test_check(at, &success, fault && !at->expected_fault, 541 "unexpected fault"); 542 ac_test_check(at, &success, !fault && at->expected_fault, 543 "unexpected access"); 544 ac_test_check(at, &success, fault && e != at->expected_error, 545 "error code %x expected %x", e, at->expected_error); 546 ac_test_check(at, &success, at->ptep && *at->ptep != at->expected_pte, 547 "pte %x expected %x", *at->ptep, at->expected_pte); 548 ac_test_check(at, &success, 549 !pt_match(*at->pdep, at->expected_pde, at->ignore_pde), 550 "pde %x expected %x", *at->pdep, at->expected_pde); 551 552 if (success && verbose) { 553 printf("PASS\n"); 554 } 555 return success; 556 } 557 558 static void ac_test_show(ac_test_t *at) 559 { 560 char line[5000]; 561 562 *line = 0; 563 strcat(line, "test"); 564 for (int i = 0; i < NR_AC_FLAGS; ++i) 565 if (at->flags[i]) { 566 strcat(line, " "); 567 strcat(line, ac_names[i]); 568 } 569 strcat(line, ": "); 570 printf("%s", line); 571 } 572 573 /* 574 * This test case is used to triger the bug which is fixed by 575 * commit e09e90a5 in the kvm tree 576 */ 577 static int corrupt_hugepage_triger(ac_pool_t *pool) 578 { 579 ac_test_t at1, at2; 580 581 ac_test_init(&at1, (void *)(0x123400000000)); 582 ac_test_init(&at2, (void *)(0x666600000000)); 583 584 at2.flags[AC_CPU_CR0_WP] = 1; 585 at2.flags[AC_PDE_PSE] = 1; 586 at2.flags[AC_PDE_PRESENT] = 1; 587 ac_test_setup_pte(&at2, pool); 588 if (!ac_test_do_access(&at2)) 589 goto err; 590 591 at1.flags[AC_CPU_CR0_WP] = 1; 592 at1.flags[AC_PDE_PSE] = 1; 593 at1.flags[AC_PDE_WRITABLE] = 1; 594 at1.flags[AC_PDE_PRESENT] = 1; 595 ac_test_setup_pte(&at1, pool); 596 if (!ac_test_do_access(&at1)) 597 goto err; 598 599 at1.flags[AC_ACCESS_WRITE] = 1; 600 ac_set_expected_status(&at1); 601 if (!ac_test_do_access(&at1)) 602 goto err; 603 604 at2.flags[AC_ACCESS_WRITE] = 1; 605 ac_set_expected_status(&at2); 606 if (!ac_test_do_access(&at2)) 607 goto err; 608 609 return 1; 610 611 err: 612 printf("corrupt_hugepage_triger test fail\n"); 613 return 0; 614 } 615 616 /* 617 * This test case is used to triger the bug which is fixed by 618 * commit 3ddf6c06e13e in the kvm tree 619 */ 620 static int check_pfec_on_prefetch_pte(ac_pool_t *pool) 621 { 622 ac_test_t at1, at2; 623 624 ac_test_init(&at1, (void *)(0x123406001000)); 625 ac_test_init(&at2, (void *)(0x123406003000)); 626 627 at1.flags[AC_PDE_PRESENT] = 1; 628 at1.flags[AC_PTE_PRESENT] = 1; 629 ac_setup_specific_pages(&at1, pool, 30 * 1024 * 1024, 30 * 1024 * 1024); 630 631 at2.flags[AC_PDE_PRESENT] = 1; 632 at2.flags[AC_PTE_NX] = 1; 633 at2.flags[AC_PTE_PRESENT] = 1; 634 ac_setup_specific_pages(&at2, pool, 30 * 1024 * 1024, 30 * 1024 * 1024); 635 636 if (!ac_test_do_access(&at1)) { 637 printf("%s: prepare fail\n", __FUNCTION__); 638 goto err; 639 } 640 641 if (!ac_test_do_access(&at2)) { 642 printf("%s: check PFEC on prefetch pte path fail\n", 643 __FUNCTION__); 644 goto err; 645 } 646 647 return 1; 648 649 err: 650 return 0; 651 } 652 653 int ac_test_exec(ac_test_t *at, ac_pool_t *pool) 654 { 655 int r; 656 657 if (verbose) { 658 ac_test_show(at); 659 } 660 ac_test_setup_pte(at, pool); 661 r = ac_test_do_access(at); 662 return r; 663 } 664 665 typedef int (*ac_test_fn)(ac_pool_t *pool); 666 const ac_test_fn ac_test_cases[] = 667 { 668 corrupt_hugepage_triger, 669 check_pfec_on_prefetch_pte, 670 }; 671 672 int ac_test_run(void) 673 { 674 ac_test_t at; 675 ac_pool_t pool; 676 int i, tests, successes; 677 678 printf("run\n"); 679 tests = successes = 0; 680 ac_env_int(&pool); 681 ac_test_init(&at, (void *)(0x123400000000 + 16 * smp_id())); 682 do { 683 ++tests; 684 successes += ac_test_exec(&at, &pool); 685 } while (ac_test_bump(&at)); 686 687 for (i = 0; i < ARRAY_SIZE(ac_test_cases); i++) { 688 ++tests; 689 successes += ac_test_cases[i](&pool); 690 } 691 692 printf("\n%d tests, %d failures\n", tests, tests - successes); 693 694 return successes == tests; 695 } 696 697 int main() 698 { 699 int r; 700 701 printf("starting test\n\n"); 702 r = ac_test_run(); 703 return r ? 0 : 1; 704 } 705