1/* 2 * Copyright (C) 2014 The Android Open Source Project 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#include <gtest/gtest.h> 18 19#include <dlfcn.h> 20#include <elf.h> 21#include <errno.h> 22#include <fcntl.h> 23#include <inttypes.h> 24#include <stdio.h> 25#include <string.h> 26#include <unistd.h> 27#include <android/dlext.h> 28#include <sys/mman.h> 29#include <sys/types.h> 30#include <sys/wait.h> 31 32#include <pagemap/pagemap.h> 33 34#include "TemporaryFile.h" 35 36#define ASSERT_DL_NOTNULL(ptr) \ 37 ASSERT_TRUE(ptr != nullptr) << "dlerror: " << dlerror() 38 39#define ASSERT_DL_ZERO(i) \ 40 ASSERT_EQ(0, i) << "dlerror: " << dlerror() 41 42#define ASSERT_NOERROR(i) \ 43 ASSERT_NE(-1, i) << "errno: " << strerror(errno) 44 45#define ASSERT_SUBSTR(needle, haystack) \ 46 ASSERT_PRED_FORMAT2(::testing::IsSubstring, needle, haystack) 47 48 49typedef int (*fn)(void); 50#define LIBNAME "libdlext_test.so" 51#define LIBNAME_NORELRO "libdlext_test_norelro.so" 52#define LIBSIZE 1024*1024 // how much address space to reserve for it 53 54#if defined(__LP64__) 55#define LIBPATH_PREFIX "/nativetest64/libdlext_test_fd/" 56#else 57#define LIBPATH_PREFIX "/nativetest/libdlext_test_fd/" 58#endif 59 60#define LIBPATH LIBPATH_PREFIX "libdlext_test_fd.so" 61#define LIBZIPPATH LIBPATH_PREFIX "libdlext_test_fd_zipaligned.zip" 62 63#define LIBZIP_OFFSET 2*PAGE_SIZE 64 65class DlExtTest : public ::testing::Test { 66protected: 67 virtual void SetUp() { 68 handle_ = nullptr; 69 // verify that we don't have the library loaded already 70 void* h = dlopen(LIBNAME, RTLD_NOW | RTLD_NOLOAD); 71 ASSERT_TRUE(h == nullptr); 72 h = dlopen(LIBNAME_NORELRO, RTLD_NOW | RTLD_NOLOAD); 73 ASSERT_TRUE(h == nullptr); 74 // call dlerror() to swallow the error, and check it was the one we wanted 75 ASSERT_STREQ("dlopen failed: library \"" LIBNAME_NORELRO "\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror()); 76 } 77 78 virtual void TearDown() { 79 if (handle_ != nullptr) { 80 ASSERT_DL_ZERO(dlclose(handle_)); 81 } 82 } 83 84 void* handle_; 85}; 86 87TEST_F(DlExtTest, ExtInfoNull) { 88 handle_ = android_dlopen_ext(LIBNAME, RTLD_NOW, nullptr); 89 ASSERT_DL_NOTNULL(handle_); 90 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 91 ASSERT_DL_NOTNULL(f); 92 EXPECT_EQ(4, f()); 93} 94 95TEST_F(DlExtTest, ExtInfoNoFlags) { 96 android_dlextinfo extinfo; 97 extinfo.flags = 0; 98 handle_ = android_dlopen_ext(LIBNAME, RTLD_NOW, &extinfo); 99 ASSERT_DL_NOTNULL(handle_); 100 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 101 ASSERT_DL_NOTNULL(f); 102 EXPECT_EQ(4, f()); 103} 104 105TEST_F(DlExtTest, ExtInfoUseFd) { 106 const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBPATH; 107 108 android_dlextinfo extinfo; 109 extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD; 110 extinfo.library_fd = TEMP_FAILURE_RETRY(open(lib_path.c_str(), O_RDONLY | O_CLOEXEC)); 111 ASSERT_TRUE(extinfo.library_fd != -1); 112 handle_ = android_dlopen_ext(lib_path.c_str(), RTLD_NOW, &extinfo); 113 ASSERT_DL_NOTNULL(handle_); 114 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 115 ASSERT_DL_NOTNULL(f); 116 EXPECT_EQ(4, f()); 117} 118 119TEST_F(DlExtTest, ExtInfoUseFdWithOffset) { 120 const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBZIPPATH; 121 122 android_dlextinfo extinfo; 123 extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; 124 extinfo.library_fd = TEMP_FAILURE_RETRY(open(lib_path.c_str(), O_RDONLY | O_CLOEXEC)); 125 extinfo.library_fd_offset = LIBZIP_OFFSET; 126 127 handle_ = android_dlopen_ext(lib_path.c_str(), RTLD_NOW, &extinfo); 128 ASSERT_DL_NOTNULL(handle_); 129 130 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 131 ASSERT_DL_NOTNULL(f); 132 EXPECT_EQ(4, f()); 133} 134 135TEST_F(DlExtTest, ExtInfoUseFdWithInvalidOffset) { 136 const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBZIPPATH; 137 // lib_path is relative when $ANDROID_DATA is relative 138 char lib_realpath_buf[PATH_MAX]; 139 ASSERT_TRUE(realpath(lib_path.c_str(), lib_realpath_buf) == lib_realpath_buf); 140 const std::string lib_realpath = std::string(lib_realpath_buf); 141 142 android_dlextinfo extinfo; 143 extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; 144 extinfo.library_fd = TEMP_FAILURE_RETRY(open(lib_path.c_str(), O_RDONLY | O_CLOEXEC)); 145 extinfo.library_fd_offset = 17; 146 147 handle_ = android_dlopen_ext("libname_placeholder", RTLD_NOW, &extinfo); 148 ASSERT_TRUE(handle_ == nullptr); 149 ASSERT_STREQ("dlopen failed: file offset for the library \"libname_placeholder\" is not page-aligned: 17", dlerror()); 150 151 // Test an address above 2^44, for http://b/18178121 . 152 extinfo.library_fd_offset = (5LL<<48) + PAGE_SIZE; 153 handle_ = android_dlopen_ext("libname_placeholder", RTLD_NOW, &extinfo); 154 ASSERT_TRUE(handle_ == nullptr); 155 ASSERT_SUBSTR("dlopen failed: file offset for the library \"libname_placeholder\" >= file size", dlerror()); 156 157 extinfo.library_fd_offset = 0LL - PAGE_SIZE; 158 handle_ = android_dlopen_ext("libname_placeholder", RTLD_NOW, &extinfo); 159 ASSERT_TRUE(handle_ == nullptr); 160 ASSERT_SUBSTR("dlopen failed: file offset for the library \"libname_placeholder\" is negative", dlerror()); 161 162 extinfo.library_fd_offset = PAGE_SIZE; 163 handle_ = android_dlopen_ext("libname_ignored", RTLD_NOW, &extinfo); 164 ASSERT_TRUE(handle_ == nullptr); 165 ASSERT_EQ("dlopen failed: \"" + lib_realpath + "\" has bad ELF magic", dlerror()); 166 167 close(extinfo.library_fd); 168} 169 170TEST_F(DlExtTest, ExtInfoUseOffsetWihtoutFd) { 171 android_dlextinfo extinfo; 172 extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; 173 extinfo.library_fd_offset = LIBZIP_OFFSET; 174 175 handle_ = android_dlopen_ext("/some/lib/that/does_not_exist", RTLD_NOW, &extinfo); 176 ASSERT_TRUE(handle_ == nullptr); 177 ASSERT_STREQ("dlopen failed: invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without ANDROID_DLEXT_USE_LIBRARY_FD): 0x20", dlerror()); 178} 179 180TEST(dlext, android_dlopen_ext_force_load_smoke) { 181 // 1. Open actual file 182 void* handle = dlopen("libdlext_test.so", RTLD_NOW); 183 ASSERT_DL_NOTNULL(handle); 184 // 2. Open link with force_load flag set 185 android_dlextinfo extinfo; 186 extinfo.flags = ANDROID_DLEXT_FORCE_LOAD; 187 void* handle2 = android_dlopen_ext("libdlext_test_v2.so", RTLD_NOW, &extinfo); 188 ASSERT_DL_NOTNULL(handle2); 189 ASSERT_TRUE(handle != handle2); 190 191 dlclose(handle2); 192 dlclose(handle); 193} 194 195TEST(dlext, android_dlopen_ext_force_load_soname_exception) { 196 // Check if soname lookup still returns already loaded library 197 // when ANDROID_DLEXT_FORCE_LOAD flag is specified. 198 void* handle = dlopen("libdlext_test_v2.so", RTLD_NOW); 199 ASSERT_DL_NOTNULL(handle); 200 201 android_dlextinfo extinfo; 202 extinfo.flags = ANDROID_DLEXT_FORCE_LOAD; 203 204 // Note that 'libdlext_test.so' is dt_soname for libdlext_test_v2.so 205 void* handle2 = android_dlopen_ext("libdlext_test.so", RTLD_NOW, &extinfo); 206 207 ASSERT_DL_NOTNULL(handle2); 208 ASSERT_TRUE(handle == handle2); 209 210 dlclose(handle2); 211 dlclose(handle); 212} 213 214TEST(dlfcn, dlopen_from_zip_absolute_path) { 215 const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBZIPPATH; 216 217 void* handle = dlopen((lib_path + "!/libdir/libdlext_test_fd.so").c_str(), RTLD_NOW); 218 ASSERT_TRUE(handle != nullptr) << dlerror(); 219 220 int (*fn)(void); 221 fn = reinterpret_cast<int (*)(void)>(dlsym(handle, "getRandomNumber")); 222 ASSERT_TRUE(fn != nullptr); 223 EXPECT_EQ(4, fn()); 224 225 dlclose(handle); 226} 227 228TEST(dlfcn, dlopen_from_zip_ld_library_path) { 229 const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBZIPPATH + "!/libdir"; 230 231 typedef void (*fn_t)(const char*); 232 fn_t android_update_LD_LIBRARY_PATH = 233 reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH")); 234 235 ASSERT_TRUE(android_update_LD_LIBRARY_PATH != nullptr) << dlerror(); 236 237 void* handle = dlopen("libdlext_test_fd.so", RTLD_NOW); 238 ASSERT_TRUE(handle == nullptr); 239 240 android_update_LD_LIBRARY_PATH(lib_path.c_str()); 241 242 handle = dlopen("libdlext_test_fd.so", RTLD_NOW); 243 ASSERT_TRUE(handle != nullptr) << dlerror(); 244 245 int (*fn)(void); 246 fn = reinterpret_cast<int (*)(void)>(dlsym(handle, "getRandomNumber")); 247 ASSERT_TRUE(fn != nullptr); 248 EXPECT_EQ(4, fn()); 249 250 dlclose(handle); 251} 252 253 254TEST_F(DlExtTest, Reserved) { 255 void* start = mmap(nullptr, LIBSIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 256 -1, 0); 257 ASSERT_TRUE(start != MAP_FAILED); 258 android_dlextinfo extinfo; 259 extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS; 260 extinfo.reserved_addr = start; 261 extinfo.reserved_size = LIBSIZE; 262 handle_ = android_dlopen_ext(LIBNAME, RTLD_NOW, &extinfo); 263 ASSERT_DL_NOTNULL(handle_); 264 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 265 ASSERT_DL_NOTNULL(f); 266 EXPECT_GE(reinterpret_cast<void*>(f), start); 267 EXPECT_LT(reinterpret_cast<void*>(f), 268 reinterpret_cast<char*>(start) + LIBSIZE); 269 EXPECT_EQ(4, f()); 270} 271 272TEST_F(DlExtTest, ReservedTooSmall) { 273 void* start = mmap(nullptr, PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 274 -1, 0); 275 ASSERT_TRUE(start != MAP_FAILED); 276 android_dlextinfo extinfo; 277 extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS; 278 extinfo.reserved_addr = start; 279 extinfo.reserved_size = PAGE_SIZE; 280 handle_ = android_dlopen_ext(LIBNAME, RTLD_NOW, &extinfo); 281 EXPECT_EQ(nullptr, handle_); 282} 283 284TEST_F(DlExtTest, ReservedHint) { 285 void* start = mmap(nullptr, LIBSIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 286 -1, 0); 287 ASSERT_TRUE(start != MAP_FAILED); 288 android_dlextinfo extinfo; 289 extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS_HINT; 290 extinfo.reserved_addr = start; 291 extinfo.reserved_size = LIBSIZE; 292 handle_ = android_dlopen_ext(LIBNAME, RTLD_NOW, &extinfo); 293 ASSERT_DL_NOTNULL(handle_); 294 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 295 ASSERT_DL_NOTNULL(f); 296 EXPECT_GE(reinterpret_cast<void*>(f), start); 297 EXPECT_LT(reinterpret_cast<void*>(f), 298 reinterpret_cast<char*>(start) + LIBSIZE); 299 EXPECT_EQ(4, f()); 300} 301 302TEST_F(DlExtTest, ReservedHintTooSmall) { 303 void* start = mmap(nullptr, PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 304 -1, 0); 305 ASSERT_TRUE(start != MAP_FAILED); 306 android_dlextinfo extinfo; 307 extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS_HINT; 308 extinfo.reserved_addr = start; 309 extinfo.reserved_size = PAGE_SIZE; 310 handle_ = android_dlopen_ext(LIBNAME, RTLD_NOW, &extinfo); 311 ASSERT_DL_NOTNULL(handle_); 312 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 313 ASSERT_DL_NOTNULL(f); 314 EXPECT_TRUE(reinterpret_cast<void*>(f) < start || 315 (reinterpret_cast<void*>(f) >= 316 reinterpret_cast<char*>(start) + PAGE_SIZE)); 317 EXPECT_EQ(4, f()); 318} 319 320class DlExtRelroSharingTest : public DlExtTest { 321protected: 322 virtual void SetUp() { 323 DlExtTest::SetUp(); 324 void* start = mmap(nullptr, LIBSIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 325 -1, 0); 326 ASSERT_TRUE(start != MAP_FAILED); 327 extinfo_.flags = ANDROID_DLEXT_RESERVED_ADDRESS; 328 extinfo_.reserved_addr = start; 329 extinfo_.reserved_size = LIBSIZE; 330 extinfo_.relro_fd = -1; 331 } 332 333 virtual void TearDown() { 334 DlExtTest::TearDown(); 335 } 336 337 void CreateRelroFile(const char* lib, const char* relro_file) { 338 int relro_fd = open(relro_file, O_RDWR | O_TRUNC); 339 ASSERT_NOERROR(relro_fd); 340 341 pid_t pid = fork(); 342 if (pid == 0) { 343 // child process 344 extinfo_.flags |= ANDROID_DLEXT_WRITE_RELRO; 345 extinfo_.relro_fd = relro_fd; 346 void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo_); 347 if (handle == nullptr) { 348 fprintf(stderr, "in child: %s\n", dlerror()); 349 exit(1); 350 } 351 exit(0); 352 } 353 354 // continuing in parent 355 ASSERT_NOERROR(close(relro_fd)); 356 ASSERT_NOERROR(pid); 357 int status; 358 ASSERT_EQ(pid, waitpid(pid, &status, 0)); 359 ASSERT_TRUE(WIFEXITED(status)); 360 ASSERT_EQ(0, WEXITSTATUS(status)); 361 362 // reopen file for reading so it can be used 363 relro_fd = open(relro_file, O_RDONLY); 364 ASSERT_NOERROR(relro_fd); 365 extinfo_.flags |= ANDROID_DLEXT_USE_RELRO; 366 extinfo_.relro_fd = relro_fd; 367 } 368 369 void TryUsingRelro(const char* lib) { 370 handle_ = android_dlopen_ext(lib, RTLD_NOW, &extinfo_); 371 ASSERT_DL_NOTNULL(handle_); 372 fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber")); 373 ASSERT_DL_NOTNULL(f); 374 EXPECT_EQ(4, f()); 375 } 376 377 void SpawnChildrenAndMeasurePss(const char* lib, bool share_relro, size_t* pss_out); 378 379 android_dlextinfo extinfo_; 380}; 381 382TEST_F(DlExtRelroSharingTest, ChildWritesGoodData) { 383 TemporaryFile tf; // Use tf to get an unique filename. 384 ASSERT_NOERROR(close(tf.fd)); 385 386 ASSERT_NO_FATAL_FAILURE(CreateRelroFile(LIBNAME, tf.filename)); 387 ASSERT_NO_FATAL_FAILURE(TryUsingRelro(LIBNAME)); 388 389 // Use destructor of tf to close and unlink the file. 390 tf.fd = extinfo_.relro_fd; 391} 392 393TEST_F(DlExtRelroSharingTest, ChildWritesNoRelro) { 394 TemporaryFile tf; // // Use tf to get an unique filename. 395 ASSERT_NOERROR(close(tf.fd)); 396 397 ASSERT_NO_FATAL_FAILURE(CreateRelroFile(LIBNAME_NORELRO, tf.filename)); 398 ASSERT_NO_FATAL_FAILURE(TryUsingRelro(LIBNAME_NORELRO)); 399 400 // Use destructor of tf to close and unlink the file. 401 tf.fd = extinfo_.relro_fd; 402} 403 404TEST_F(DlExtRelroSharingTest, RelroFileEmpty) { 405 ASSERT_NO_FATAL_FAILURE(TryUsingRelro(LIBNAME)); 406} 407 408TEST_F(DlExtRelroSharingTest, VerifyMemorySaving) { 409 if (geteuid() != 0) { 410 GTEST_LOG_(INFO) << "This test must be run as root.\n"; 411 return; 412 } 413 414 TemporaryFile tf; // Use tf to get an unique filename. 415 ASSERT_NOERROR(close(tf.fd)); 416 417 ASSERT_NO_FATAL_FAILURE(CreateRelroFile(LIBNAME, tf.filename)); 418 419 int pipefd[2]; 420 ASSERT_NOERROR(pipe(pipefd)); 421 422 size_t without_sharing, with_sharing; 423 ASSERT_NO_FATAL_FAILURE(SpawnChildrenAndMeasurePss(LIBNAME, false, &without_sharing)); 424 ASSERT_NO_FATAL_FAILURE(SpawnChildrenAndMeasurePss(LIBNAME, true, &with_sharing)); 425 426 // We expect the sharing to save at least 10% of the total PSS. In practice 427 // it saves 40%+ for this test. 428 size_t expected_size = without_sharing - (without_sharing/10); 429 EXPECT_LT(with_sharing, expected_size); 430 431 // Use destructor of tf to close and unlink the file. 432 tf.fd = extinfo_.relro_fd; 433} 434 435void getPss(pid_t pid, size_t* pss_out) { 436 pm_kernel_t* kernel; 437 ASSERT_EQ(0, pm_kernel_create(&kernel)); 438 439 pm_process_t* process; 440 ASSERT_EQ(0, pm_process_create(kernel, pid, &process)); 441 442 pm_map_t** maps; 443 size_t num_maps; 444 ASSERT_EQ(0, pm_process_maps(process, &maps, &num_maps)); 445 446 size_t total_pss = 0; 447 for (size_t i = 0; i < num_maps; i++) { 448 pm_memusage_t usage; 449 ASSERT_EQ(0, pm_map_usage(maps[i], &usage)); 450 total_pss += usage.pss; 451 } 452 *pss_out = total_pss; 453 454 free(maps); 455 pm_process_destroy(process); 456 pm_kernel_destroy(kernel); 457} 458 459void DlExtRelroSharingTest::SpawnChildrenAndMeasurePss(const char* lib, bool share_relro, 460 size_t* pss_out) { 461 const int CHILDREN = 20; 462 463 // Create children 464 pid_t childpid[CHILDREN]; 465 int childpipe[CHILDREN]; 466 for (int i=0; i<CHILDREN; ++i) { 467 char read_buf; 468 int child_done_pipe[2], parent_done_pipe[2]; 469 ASSERT_NOERROR(pipe(child_done_pipe)); 470 ASSERT_NOERROR(pipe(parent_done_pipe)); 471 472 pid_t child = fork(); 473 if (child == 0) { 474 // close the 'wrong' ends of the pipes in the child 475 close(child_done_pipe[0]); 476 close(parent_done_pipe[1]); 477 478 // open the library 479 void* handle; 480 if (share_relro) { 481 handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo_); 482 } else { 483 handle = dlopen(lib, RTLD_NOW); 484 } 485 if (handle == nullptr) { 486 fprintf(stderr, "in child: %s\n", dlerror()); 487 exit(1); 488 } 489 490 // close write end of child_done_pipe to signal the parent that we're done. 491 close(child_done_pipe[1]); 492 493 // wait for the parent to close parent_done_pipe, then exit 494 read(parent_done_pipe[0], &read_buf, 1); 495 exit(0); 496 } 497 498 ASSERT_NOERROR(child); 499 500 // close the 'wrong' ends of the pipes in the parent 501 close(child_done_pipe[1]); 502 close(parent_done_pipe[0]); 503 504 // wait for the child to be done 505 read(child_done_pipe[0], &read_buf, 1); 506 close(child_done_pipe[0]); 507 508 // save the child's pid and the parent_done_pipe 509 childpid[i] = child; 510 childpipe[i] = parent_done_pipe[1]; 511 } 512 513 // Sum the PSS of all the children 514 size_t total_pss = 0; 515 for (int i=0; i<CHILDREN; ++i) { 516 size_t child_pss; 517 ASSERT_NO_FATAL_FAILURE(getPss(childpid[i], &child_pss)); 518 total_pss += child_pss; 519 } 520 *pss_out = total_pss; 521 522 // Close pipes and wait for children to exit 523 for (int i=0; i<CHILDREN; ++i) { 524 ASSERT_NOERROR(close(childpipe[i])); 525 } 526 for (int i=0; i<CHILDREN; ++i) { 527 int status; 528 ASSERT_EQ(childpid[i], waitpid(childpid[i], &status, 0)); 529 ASSERT_TRUE(WIFEXITED(status)); 530 ASSERT_EQ(0, WEXITSTATUS(status)); 531 } 532} 533