应用程序加载

load C++ main 加载顺序

在了解应用程序加载顺序之前,我们需要先了解一下runtime中比较常用的方法load以及C++方法、main函数的执行顺序

  • ViewController.m文件中添加load方法,并在方法中打断点
+ (void)load {NSLog(@"%s",__func__);
}
  • main.m文件中增加C++方法,并且在方法中打断点
__attribute__((constructor)) void kcFunc() {printf("来了 : %s \n",__func__);
}
  • main函数中打断点
int main(int argc, char * argv[]) {NSString * appDelegateClassName;NSLog(@"1223333");@autoreleasepool {appDelegateClassName = NSStringFromClass([AppDelegate class]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
  • 运行查看结果
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6e3QwKT-1601274637503)(media/16011714502276/16011735805264.jpg)]
    由打印结果可知,这三个函数的加载顺序为:load -> C++ -> main,可仍然会有疑问
    思考:1. 按照之前的理解,main是程序的第一个入口,但load比mian先执行,那么入口到底是什么?2. load函数是谁在调用

load 函数之前,系统做了什么

带着上述疑问,我们在load函数内加断点,并使用bt命令查看堆栈信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DyDU50K1-1601274637506)(media/16011714502276/16011748928324.jpg)]
使用bt命令打印堆栈信息,可知:_dyld_start为整个项目的第一个函数入口,该函数在dyld框架内,从dyld源码地址下载源码,改源码无法运行起来,只能打开查看
如果读bt命令打印出的堆栈信息,可以直接查看左侧的堆栈目录也是一样的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-guifKhu0-1601274637507)(media/16011714502276/16011764196871.jpg)]

_dyld_start方法

dyld源码中全局搜索_dyld_start方法,在dyld底层,该方法是做了底层环境区分的,虽然每个环境的编译大致相同,但还是建议查看arm64的源码

#if __arm64__.text.align 2.globl __dyld_start
__dyld_start:mov 	x28, spand     sp, x28, #~15		// force 16-byte alignment of stackmov	x0, #0mov	x1, #0stp	x1, x0, [sp, #-16]!	// make aligned terminating framemov	fp, sp			// set up fp to point to terminating framesub	sp, sp, #16             // make room for local variablesldr     x0, [x28]               // get app's mh into x0ldr     x1, [x28, #8]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)add     x2, x28, #16            // get argv into x2adrp	x3,___dso_handle@pageadd 	x3,x3,___dso_handle@pageoff // get dyld's mh in to x4mov	x4,sp                   // x5 has &startGlue// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)// 调用 dyldbootstrap::start方法bl	__ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pmmov	x16,x0                  // save entry point address in x16ldr     x1, [sp]cmp	x1, #0b.ne	Lnew// LC_UNIXTHREAD way, clean up stack and jump to resultadd	sp, x28, #8             // restore unaligned stack pointer without app mhbraaz   x16                     // jump to the program's entry point// LC_MAIN case, set up stack for call to main()
Lnew:	mov	lr, x1		    // simulate return address into _start in libdyld.dylib
#if __LP64__ldr	x0, [x28, #8]       // main param1 = argcadd	x1, x28, #16        // main param2 = argvadd	x2, x1, x0, lsl #3add	x2, x2, #8          // main param3 = &env[0]mov	x3, x2
Lapple:	ldr	x4, [x3]add	x3, x3, #8#endifcmp	x4, #0b.ne	Lapple		    // main param4 = apple
#if __arm64e__braaz   x16#endif

源码有点长,我对代码做了一些删减,删减部分主要是if-else部分,可暂时不去查看,但是apple的注释写的还是不错的,英文好的可以直接查看,不好的用翻译软件也是可以的。在源码中可以看到call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)注释,调用 dyldbootstrap::start方法,根据堆栈信息可知该方法为_dyld_start方法执行后的第二个方法,全局搜索,找到该方法

dyldbootstrap::start()

全局搜索start方法,最终我们在dyldbootstrap.cpp文件中找到该函数。
仍然查看源码以及注释,源码如下

// 这是引导dyld的代码。这项工作通常是有dyld和crt完成的。在dyld中我们必须手动执行此操作
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{// Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);// if kernel had to slide dyld, we need to fix up load sensitive locations// we have to do this before using any global variablesrebaseDyld(dyldsMachHeader);// kernel sets up env pointer to be just past end of agv arrayconst char** envp = &argv[argc+1];// kernel sets up apple pointer to be just past end of envp arrayconst char** apple = envp;while(*apple != NULL) { ++apple; }++apple;// set up random value for stack canary__guard_setup(apple);#if DYLD_INITIALIZER_SUPPORT// run all C++ initializers inside dyldrunDyldInitializers(argc, argv, envp, apple);
#endif// now that we are done bootstrapping dyld, call dyld's mainuintptr_t appsSlide = appsMachHeader->getSlide();return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

解释

  • 该函数是dyld的引导,这部分代码需要手动执行,也就是在上一个位置_dyld_start中进行调用
  • 如果没有运行C++初始化程序时,dyld调用runDyldInitializers(argc, argv, envp, apple);方法进行所有C++初始化程序的运行。
  • 完成引导程序后,调用dyld::_main方法进行下一步操作
  • 这里的堆栈信息有些许不正确是因为,我在调试时使用的是Simulator模拟器,而查看的信息为arm64真机环境。

dyld::_main方法

该函数的的源码有点长,而且基本删减不了什么,需要耐心查看,源码如下

// dyld的入口点,内核加载dyld并跳转到 __dyld_start , 后者设置一些寄存器并调用此函数
// 返回目标程序 __dyld_start 跳转到 main() 的地址
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue)
{if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0);}// 检查是否有内核标志dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));// 从环境中获取 main 可执行文件的 cdHashuint8_t mainExecutableCDHashBuffer[20];const uint8_t* mainExecutableCDHash = nullptr;if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )mainExecutableCDHash = mainExecutableCDHashBuffer;#if !TARGET_OS_SIMULATOR// 通知 dyld 的加载notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));// 通知 main 可执行文件的加载notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endifuintptr_t result = 0;sMainExecutableMachHeader = mainExecutableMH;sMainExecutableSlide = mainExecutableSlide;// Set the platform ID in the all image infos so debuggers can tell the process type// 给所有的image设置平台ID,以便调试器可以告诉进行类型// FIXME: This can all be removed once we make the kernel handle it in rdar://43369446if (gProcessInfo->version >= 16) {__block bool platformFound = false;((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {if (platformFound) {halt("MH_EXECUTE binaries may only specify one platform");}gProcessInfo->platform = (uint32_t)platform;platformFound = true;});if (gProcessInfo->platform == (uint32_t)dyld3::Platform::unknown) {// There were no platforms found in the binary. This may occur on macOS for alternate toolchains and old binaries.// 它永远不会再我们任何嵌入式平台上发生,只能发生于macOS上}}CRSetCrashLogMessage("dyld: launch started");// 获取上下文 -- 这个函数中是对 gLinkContent 进行一系列的配置setContext(mainExecutableMH, argc, argv, envp, apple);// Pickup the pointer to the exec path.// 获取执行exec路径的指针,exec就是编译后的可执行文件。sExecPath = _simple_getenv(apple, "executable_path");// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld// 从 dyld 移除临时 apple[0] 过渡代码if (!sExecPath) sExecPath = apple[0];#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_OS_SIMULATOR// <rdar://54095622> kernel is not passing a real path for main executable// 内核没有为 main 可执行文件传递真实路径if ( strncmp(sExecPath, "/var/containers/Bundle/Application/", 35) == 0 ) {if ( char* newPath = (char*)malloc(strlen(sExecPath)+10) ) {// 设置新路径strcpy(newPath, "/private");strcat(newPath, sExecPath);sExecPath = newPath;}}
#endifif ( sExecPath[0] != '/' ) {// 有相对路径,需要使用 cmd 获取绝对路径char cwdbuff[MAXPATHLEN];if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {// maybe use static buffer to avoid calling malloc so early...// 可用静态缓冲区内容来避免过早的调用 mallocchar* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];strcpy(s, cwdbuff);strcat(s, "/");strcat(s, sExecPath);sExecPath = s;}}// 记录进场简称,以便以后记录sExecShortName = ::strrchr(sExecPath, '/');if ( sExecShortName != NULL )++sExecShortName;elsesExecShortName = sExecPath;// 配置主流程 -- 内容仍然是配置 gLinkContext 内容configureProcessRestrictions(mainExecutableMH, envp);// Check if we should force dyld3.  Note we have to do this outside of the regular env parsing due to AMFI// 检查是否应强制使用dyld3if ( dyld3::internalInstall() ) {if (const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")) {if ( strcmp(useClosures, "0") == 0 ) {sClosureMode = ClosureMode::Off;} else if ( strcmp(useClosures, "1") == 0 ) {// 只有macOS能够设置} else {// 报警告dyld::warn("unknown option to DYLD_USE_CLOSURES.  Valid options are: 0 and 1\n");}}}// 检查环境变量checkEnvironmentVariables(envp);// 默认未初始化的后背路径defaultUninitializedFallbackPaths(envp);if ( sEnv.DYLD_PRINT_OPTS )// 打印选项列表printOptions(argv);if ( sEnv.DYLD_PRINT_ENV ) // 打印环境变量列表printEnvironmentVariables(envp);// Parse this envirionment variable outside of the regular logic as we want to accept// this on binaries without an entitelment// 解析常规逻辑之外的环境变量,因为我们想在二进制文件上接受此变量而没有权限
#if !TARGET_OS_SIMULATORif ( _simple_getenv(envp, "DYLD_JUST_BUILD_CLOSURE") != nullptr ) {
#if TARGET_OS_IPHONEconst char* tempDir = getTempDir(envp);if ( (tempDir != nullptr) && (geteuid() != 0) ) {// 使用真实路径,防止出现类似 TMPRIR=/tmp/../usr/bin 的路径char realPath[PATH_MAX];if ( realpath(tempDir, realPath) != NULL )tempDir = realPath;if (strncmp(tempDir, "/private/var/mobile/Containers/", strlen("/private/var/mobile/Containers/")) == 0) {sJustBuildClosure = true;}}
#endif// If we didn't like the format of TMPDIR, just exit.  We don't want to launch the app as that would bring up the UI// 路径不正确时直接退出if (!sJustBuildClosure) {_exit(EXIT_SUCCESS);}}
#endifif ( sJustBuildClosure )// 模式设置:on意味着我们设置DYLD_USE_CLOUSURES = 1,有env变量或iOS上的客户缓存sClosureMode = ClosureMode::On;// 获取Host信息getHostInfo(mainExecutableMH, mainExecutableSlide);// 加载共享缓存,iOS无法在没有共享缓存的情况下运行checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATORif ( sSharedCacheOverrideDir)mapSharedCache();
#else// 共享缓存映射mapSharedCache();
#endif}// 如果没有设置ClosureMode,需要检查环境和缓存类型if ( sClosureMode == ClosureMode::Unset ) {// First test to see if we forced in dyld2 via a kernel boot-argif ( dyld3::BootArgs::forceDyld2() ) {sClosureMode = ClosureMode::Off;} else if ( inDenyList(sExecPath) ) {sClosureMode = ClosureMode::Off;} else if ( sEnv.hasOverride ) {sClosureMode = ClosureMode::Off;} else if ( dyld3::BootArgs::forceDyld3() ) {sClosureMode = ClosureMode::On;} else {sClosureMode = getPlatformDefaultClosureMode();}}#if !TARGET_OS_SIMULATORif ( sClosureMode == ClosureMode::Off ) {if ( gLinkContext.verboseWarnings )dyld::log("dyld: not using closure because of DYLD_USE_CLOSURES or -force_dyld2=1 override\n");} else {const dyld3::closure::LaunchClosure* mainClosure = nullptr;dyld3::closure::LoadedFileInfo mainFileInfo;mainFileInfo.fileContent = mainExecutableMH;mainFileInfo.path = sExecPath;// FIXME: If we are saving this closure, this slice offset/length is probably wrong in the case of FAT files.mainFileInfo.sliceOffset = 0;mainFileInfo.sliceLen = -1;struct stat mainExeStatBuf;if ( ::stat(sExecPath, &mainExeStatBuf) == 0 ) {mainFileInfo.inode = mainExeStatBuf.st_ino;mainFileInfo.mtime = mainExeStatBuf.st_mtime;}// 检查缓存是否关闭if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);if ( gLinkContext.verboseWarnings && (mainClosure != nullptr) )dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size());}// We only want to try build a closure at runtime if its an iOS third party binary, or a macOS binary from the shared cache// 我们只想尝试在运行时构建闭包(如果它是iOS第三方二进制文件或者来自共享缓存的macOS二进制文件)// 是否允许Closure重建bool allowClosureRebuilds = false;if ( sClosureMode == ClosureMode::On ) {allowClosureRebuilds = true;} else if ( (sClosureMode == ClosureMode::PreBuiltOnly) && (mainClosure != nullptr) ) {allowClosureRebuilds = true;}if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) )mainClosure = nullptr;// 如果没有找到有效的closure缓存,将尝试构建一个新的closureif ( (mainClosure == nullptr) && allowClosureRebuilds ) {// if forcing closures, and no closure in cache, or it is invalid, check for cached closureif ( !sForceInvalidSharedCacheClosureFormat )mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);if ( mainClosure == nullptr ) {// if  no cached closure found, build new onemainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);}}// 生成闭包后退出dyld,不运行程序if ( sJustBuildClosure )_exit(EXIT_SUCCESS);// try using launch closureif ( mainClosure != nullptr ) {CRSetCrashLogMessage("dyld3: launch started");// 构建所有已知的 image 列表bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,mainExecutableSlide, argc, argv, envp, apple, &result, startGlue);if ( !launched && allowClosureRebuilds ) {// closure is out of date, build new one// chosure超时,创建新的mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);if ( mainClosure != nullptr ) {launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,mainExecutableSlide, argc, argv, envp, apple, &result, startGlue);}}if ( launched ) {gLinkContext.startedInitializingMainExecutable = true;
#if __has_feature(ptrauth_calls)// start() calls the result pointer as a function pointer so we need to sign it.// start() 将结果指针作为函数指针调用,因此我们需要对其进行签名。result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endifif (sSkipMain)result = (uintptr_t)&fake_main;return result;}else {if ( gLinkContext.verboseWarnings ) {dyld::log("dyld: unable to use closure %p\n", mainClosure);}}}}
#endif // TARGET_OS_SIMULATOR // 无法使用closure信息,以旧方式启动// 安装gdb通知程序stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);// 使初始分配足够大,以至于不太可能需要重新分配sImageRoots.reserve(16);sAddImageCallbacks.reserve(4);sRemoveImageCallbacks.reserve(4);sAddLoadImageCallbacks.reserve(4);sImageFilesNeedingTermination.reserve(16);sImageFilesNeedingDOFUnregistration.reserve(8);#if !TARGET_OS_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE// <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation processWAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endiftry {// 将dyld本身添加到UUID列表addDyldImageToUUIDList();#if SUPPORT_ACCELERATE_TABLES
#if __arm64e__// Disable accelerator tables when we have threaded rebase/bind, which is arm64e executables only for now.if (sMainExecutableMachHeader->cpusubtype == CPU_SUBTYPE_ARM64E)sDisableAcceleratorTables = true;
#endifbool mainExcutableAlreadyRebased = false;if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {struct stat statBuf;if ( ::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);}reloadAllImages:
#endif// simulators, iOS, tvOS, watchOS, are always strictgLinkContext.strictMachORequired = true;CRSetCrashLogMessage(sLoadingCrashMessage);// 为main可执行文件实例化ImageLoadersMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);gLinkContext.mainExecutable = sMainExecutable;gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);#if TARGET_OS_SIMULATOR// check main executable is not too new for this OS{if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) {throwf("program was built for a platform that is not supported by this runtime");}uint32_t mainMinOS = sMainExecutable->minOSVersion();// dyld is always built for the current OS, so we can get the current OS version// from the load command in dyld itself.uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle);if ( mainMinOS > dyldMinOS ) {#if TARGET_OS_WATCHthrowf("app was built for watchOS %d.%d which is newer than this simulator %d.%d",mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));#elif TARGET_OS_TVthrowf("app was built for tvOS %d.%d which is newer than this simulator %d.%d",mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));#elsethrowf("app was built for iOS %d.%d which is newer than this simulator %d.%d",mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));#endif}}
#endif#if SUPPORT_ACCELERATE_TABLESsAllImages.reserve((sAllCacheImagesProxy != NULL) ? 16 : INITIAL_IMAGE_COUNT);#elsesAllImages.reserve(INITIAL_IMAGE_COUNT);#endif// Now that shared cache is loaded, setup an versioned dylib overrides#if SUPPORT_VERSIONED_PATHS// 检查版本化路径checkVersionedPaths();#endif// dyld_all_image_infos image list does not contain dyld// add it as dyldPath field in dyld_all_image_infos// for simulator, dyld_sim is in image list, need host dyld added
#if TARGET_OS_SIMULATOR// get path of host dyld from table of syscall vectors in host dyldvoid* addressInDyld = gSyscallHelpers;
#else// get path of dyld itselfvoid*  addressInDyld = (void*)&__dso_handle;
#endifchar dyldPathBuffer[MAXPATHLEN+1];int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);if ( len > 0 ) {dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned stringif ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )gProcessInfo->dyldPath = strdup(dyldPathBuffer);}// load any inserted librariesif	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) // 加载插入的动态库loadInsertedDylib(*lib);}// record count of inserted libraries so that a flat search will look at // inserted libraries, then main, then others.sInsertedDylibCount = sAllImages.size()-1;// link main executablegLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLESif ( mainExcutableAlreadyRebased ) {// previous link() on main executable has already adjusted its internal pointers for ASLR// work around that by rebasing by inverse amountsMainExecutable->rebase(gLinkContext, -mainExecutableSlide);}
#endif// 链接主可执行文件link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);sMainExecutable->setNeverUnloadRecursive();if ( sMainExecutable->forceFlat() ) {gLinkContext.bindFlat = true;gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;}// link any inserted libraries// do this after linking main executable so that any dylibs pulled in by inserted // dylibs (e.g. libSystem) will not be in front of dylibs the program usesif ( sInsertedDylibCount > 0 ) {for(unsigned int i=0; i < sInsertedDylibCount; ++i) {ImageLoader* image = sAllImages[i+1];// 循环链接其他可执行文件link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);image->setNeverUnloadRecursive();}if ( gLinkContext.allowInterposing ) {// only INSERTED libraries can interpose// register interposing info after all inserted libraries are bound so chaining worksfor(unsigned int i=0; i < sInsertedDylibCount; ++i) {ImageLoader* image = sAllImages[i+1];image->registerInterposing(gLinkContext);}}}if ( gLinkContext.allowInterposing ) {// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIESfor (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {ImageLoader* image = sAllImages[i];if ( image->inSharedCache() )continue;image->registerInterposing(gLinkContext);}}#if SUPPORT_ACCELERATE_TABLESif ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) {// Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabledImageLoader::clearInterposingTuples();// unmap all loaded dylibs (but not main executable)for (long i=1; i < sAllImages.size(); ++i) {ImageLoader* image = sAllImages[i];if ( image == sMainExecutable )continue;if ( image == sAllCacheImagesProxy )continue;image->setCanUnload();ImageLoader::deleteImage(image);}// note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator tablesAllImages.clear();sImageRoots.clear();sImageFilesNeedingTermination.clear();sImageFilesNeedingDOFUnregistration.clear();sAddImageCallbacks.clear();sRemoveImageCallbacks.clear();sAddLoadImageCallbacks.clear();sAddBulkLoadImageCallbacks.clear();sDisableAcceleratorTables = true;sAllCacheImagesProxy = NULL;sMappedRangesStart = NULL;mainExcutableAlreadyRebased = true;gLinkContext.linkingMainExecutable = false;resetAllImages();goto reloadAllImages;}#endif// apply interposing to initial set of imagesfor(int i=0; i < sImageRoots.size(); ++i) {sImageRoots[i]->applyInterposing(gLinkContext);}ImageLoader::applyInterposingToDyldCache(gLinkContext);// Bind and notify for the main executable now that interposing has been registereduint64_t bindMainExecutableStartTime = mach_absolute_time();sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);uint64_t bindMainExecutableEndTime = mach_absolute_time();ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;gLinkContext.notifyBatch(dyld_image_state_bound, false);// Bind and notify for the inserted images now interposing has been registeredif ( sInsertedDylibCount > 0 ) {for(unsigned int i=0; i < sInsertedDylibCount; ++i) {ImageLoader* image = sAllImages[i+1];image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);}}// <rdar://problem/12186933> do weak binding only after all inserted images linked// 弱引用绑定主可执行文件sMainExecutable->weakBind(gLinkContext);gLinkContext.linkingMainExecutable = false;sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);CRSetCrashLogMessage("dyld: launch, running initializers");#if SUPPORT_OLD_CRT_INITIALIZATION// Old way is to run initializers via a callback from crt1.oif ( ! gRunInitializersOldWay ) initializeMainExecutable(); #else// 运行所有初始化程序initializeMainExecutable(); #endif// 通知dyld可以进入main()函数notifyMonitoringDyldMain();if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);}ARIADNEDBG_CODE(220, 1);{// find entry point for main executableresult = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();if ( result != 0 ) {// main executable uses LC_MAIN, we need to use helper in libdyld to call into main()if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;elsehalt("libdyld.dylib support not present for LC_MAIN");}else {// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();*startGlue = 0;}}
#if __has_feature(ptrauth_calls)// start() calls the result pointer as a function pointer so we need to sign it.result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif}catch(const char* message) {syncAllImages();halt(message);}catch(...) {dyld::log("dyld: launch failed\n");}CRSetCrashLogMessage("dyld2 mode");
#if !TARGET_OS_SIMULATORif (sLogClosureFailure) {// We failed to launch in dyld3, but dyld2 can handle it. synthesize a crash report for analyticsdyld3::syntheticBacktrace("Could not generate launchClosure, falling back to dyld2", true);}
#endifif (sSkipMain) {notifyMonitoringDyldMain();if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);}ARIADNEDBG_CODE(220, 1);result = (uintptr_t)&fake_main;*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;}return result;
}

大致流程:

  • 获取main可执行文件的cdHash
  • 通知dyld的加载
  • 通知main可执行文件的加载
  • 设置平台ID
  • 设置LinkContext
  • 开启ClosureMode
  • 加载共享缓存mapSharedCache
  • 安装gdb通知stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB)
  • 实例化主程序instantiateFromLoadedImage
  • 加载插入的动态库loadInsertedDylib
  • 链接主程序link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
  • 链接插入的动态库link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
  • 弱引用绑定主程序weakBind
  • 初始化主程序initializeMainExecutable
  • 通知dyld可以进main()函数了notifyMonitoringDyldMain
  • 执行load方法
    到这里,我们基本了解了load之前的加载,继续探讨

initializeMainExecutable 加载main可执行文件

根据_main方法源码以及LLDB的堆栈信息可知,该方法是_main内部执行的,源码如下

void initializeMainExecutable()
{// record that we've reached this stepgLinkContext.startedInitializingMainExecutable = true;// run initialzers for any inserted dylibs// 加载并插入所有的dylibImageLoader::InitializerTimingList initializerTimes[allImagesCount()];initializerTimes[0].count = 0;const size_t rootCount = sImageRoots.size();if ( rootCount > 1 ) {for(size_t i=1; i < rootCount; ++i) {sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);}}// run initializers for main executable and everything it brings up // 运行main可执行文件sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);// register cxa_atexit() handler to run static terminators in all loaded images when this process exitsif ( gLibSystemHelpers != NULL ) (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);// dump info if requestedif ( sEnv.DYLD_PRINT_STATISTICS )ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}

大致流程:

  • 运行并插入所有的dylib文件
  • 运行mian可执行文件
  • 加载ImageLoader

ImageLoader::runInitializers 运行main可执行文件

查看runInitializers源码,源码如下

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{uint64_t t1 = mach_absolute_time();mach_port_t thisThread = mach_thread_self();ImageLoader::UninitedUpwards up;up.count = 1;up.imagesAndPaths[0] = { this, this->getPath() };// 递归加载dylibprocessInitializers(context, thisThread, timingInfo, up);context.notifyBatch(dyld_image_state_initialized, false);mach_port_deallocate(mach_task_self(), thisThread);uint64_t t2 = mach_absolute_time();fgTotalInitTime += (t2 - t1);
}

processInitializers 加载dylib

源码如下:

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{uint32_t maxImageCount = context.imageCount()+2;ImageLoader::UninitedUpwards upsBuffer[maxImageCount];ImageLoader::UninitedUpwards& ups = upsBuffer[0];ups.count = 0;// Calling recursive init on all images in images list, building a new list of// uninitialized upward dependencies.for (uintptr_t i=0; i < images.count; ++i) {// 递归初始化images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);}// If any upward dependencies remain, init them.if ( ups.count > 0 )processInitializers(context, thisThread, timingInfo, ups);
}

recursiveInitialization 递归初始化

源码如下

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{recursive_lock lock_info(this_thread);recursiveSpinLock(lock_info);if ( fState < dyld_image_state_dependents_initialized-1 ) {uint8_t oldState = fState;// break cyclesfState = dyld_image_state_dependents_initialized-1;try {// initialize lower level libraries firstfor(unsigned int i=0; i < libraryCount(); ++i) {ImageLoader* dependentImage = libImage(i);if ( dependentImage != NULL ) {// don't try to initialize stuff "above" me yetif ( libIsUpward(i) ) {uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };uninitUps.count++;}else if ( dependentImage->fDepth >= fDepth ) {dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);}}}// record termination orderif ( this->needsTermination() )context.terminationRecorder(this);// let objc know we are about to initialize this imageuint64_t t1 = mach_absolute_time();fState = dyld_image_state_dependents_initialized;oldState = fState;context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);// initialize this imagebool hasInitializers = this->doInitialization(context);// let anyone know we finished initializing this imagefState = dyld_image_state_initialized;oldState = fState;context.notifySingle(dyld_image_state_initialized, this, NULL);if ( hasInitializers ) {uint64_t t2 = mach_absolute_time();timingInfo.addTime(this->getShortName(), t2-t1);}}catch (const char* msg) {// this image is not initializedfState = oldState;recursiveSpinUnLock();throw;}}recursiveSpinUnLock();
}
  • 执行doInitialization方法

doInitialization 初始化

源码如下

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{CRSetCrashLogMessage2(this->getPath());// mach-o has -init and static initializersdoImageInit(context);doModInitFunctions(context);CRSetCrashLogMessage2(NULL);return (fHasDashInit || fHasInitializers);
}
  • 执行doImageInitimage初始化方法
  • 执行doModInitFunctions

doImageInit 初始化方法

源码如下:

void ImageLoaderMachO::doImageInit(const LinkContext& context)
{if ( fHasDashInit ) {const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];const struct load_command* cmd = cmds;for (uint32_t i = 0; i < cmd_count; ++i) {switch (cmd->cmd) {case LC_ROUTINES_COMMAND:Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
#if __has_feature(ptrauth_calls)func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif// <rdar://problem/8543820&9228031> verify initializers are in imageif ( ! this->containsAddress(stripPointer((void*)func)) ) {dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());}if ( ! dyld::gProcessInfo->libSystemInitialized ) {// <rdar://problem/17973316> libSystem initializer must run firstdyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());}if ( context.verboseInit )dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());{dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);func(context.argc, context.argv, context.envp, context.apple, &context.programVars);}break;}cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);}}
}
  • 循环加载image,但libSystem.dylib必须先运行

notifySingle 通知表

源码如下:

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);if ( handlers != NULL ) {dyld_image_info info;info.imageLoadAddress	= image->machHeader();info.imageFilePath		= image->getRealPath();info.imageFileModDate	= image->lastModified();for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {const char* result = (*it)(state, 1, &info);if ( (result != NULL) && (state == dyld_image_state_mapped) ) {//fprintf(stderr, "  image rejected by handler=%p\n", *it);// make copy of thrown string so that later catch clauses can free itconst char* str = strdup(result);throw str;}}}if ( state == dyld_image_state_mapped ) {// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cacheif ( !image->inSharedCache() ) {dyld_uuid_info info;if ( image->getUUID(info.imageUUID) ) {info.imageLoadAddress = image->machHeader();addNonSharedCacheImageUUID(info);}}}if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {uint64_t t0 = mach_absolute_time();dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());uint64_t t1 = mach_absolute_time();uint64_t t2 = mach_absolute_time();uint64_t timeInObjC = t1-t0;uint64_t emptyTime = (t2-t1)*100;if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {timingInfo->addTime(image->getShortName(), timeInObjC);}}// mach message csdlc about dynamically unloaded imagesif ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {notifyKernel(*image, false);const struct mach_header* loadAddress[] = { image->machHeader() };const char* loadPath[] = { image->getPath() };notifyMonitoringDyld(true, 1, loadAddress, loadPath);}
}

整体流程如下

  1. dyld_startdyld入口
  2. dyldbootstrap::start
  3. dyld::_maindyld的main入口
  4. mapSharedCache共享缓存加载
  5. instantiateFromLoadedImage 实例化主程序
  6. loadInsertedDylib加载插入的动态库
  7. link主程序
  8. link插入的动态库
  9. weakBind弱引用绑定主程序
  10. initializeMainExecutable初始化主程序
  11. notifyMonitoringDyldMain通知dyld可以进mian()函数
    在这里插入图片描述
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 如何解决:Xshell拖不进去文件夹或者软件包的问题

    问题 问题&#xff1a;打开Xshell之后&#xff0c;准备想上传软件包的&#xff0c;发现拖不进去 解决办法 1、下载 lrzsz。 [rootlocalhost opt]# yum -y install lrzsz2、安装好之后&#xff0c;就可以直接拖软件包或者文件夹之类的了。 也可以用 3、如果拉不进去&#xff…...

    2024/5/8 19:23:37
  2. AJAX异步与参数

    AJAX异步与参数 开发工具与关键技术&#xff1a;AJAX 作者&#xff1a;唐文坚 撰写时间&#xff1a;2020.9.27 jQuery.ajax(url,[settings]) 概述: 通过 HTTP 请求加载远程数据。 jQuery 底层 AJAX 实现。简单易用的高层实现见 $.get, $.post 等。$.ajax() 返回其创建的 XMLHt…...

    2024/4/20 8:33:05
  3. 禁止开发者工具

    // 禁止按钮F12 document.onkeydown function (event) {var ev event || window.event || arguments.callee.caller.arguments[0];console.log(event.keyCode)if (event.keyCode 123) {return false; //123 代表F12键位} } // 禁止右键 document.oncontextmenu function (e…...

    2024/5/9 4:03:03
  4. 这才是分布式事务的正确打开方式!

    背景 随着微服务的普及&#xff0c;分布式事务成为了系统设计中不得不面对的一个问题&#xff0c;而分布式事务的实现则十分复杂。阅读本文之前&#xff0c;需要你对数据库事务的ACID、CAP理论、Base理论以及两阶段提交有一定的认知&#xff0c;不熟悉者请自行百度或者阅读参考…...

    2024/5/8 23:58:31
  5. 用户运营看这里就够了

    用户运营的核心运营指标包括了引流、活跃、留存和转化四个部分&#xff0c;而在具体的用户运营操作上&#xff0c;有广义的用户运营和微观的用户运营两个层面。 广义层面上&#xff0c;用户运营就是主要围绕上述四个操作指标进行的&#xff0c;就是说用户各个维度都是最重要的&…...

    2024/4/15 18:08:53
  6. 吹爆!阿里新产Spring源码高级笔记,原来看懂源码如此简单

    Spring的影响力想必无需与大家多说&#xff0c;如果你用spring&#xff0c;那么读读源码有助于对你最重要的工具的理解&#xff0c;好的框架源码也可以帮助我们理解什么是好代码。 刚参加工作那会&#xff0c;没想过去读源码&#xff0c;更没想过去改框架的源码&#xff1b;甚…...

    2024/5/8 17:28:12
  7. 每日一个知识点:Volatile 和 CAS 的弊端之总线风暴

    每日一个知识点系列的目的是针对某一个知识点进行概括性总结&#xff0c;可在一分钟内完成知识点的阅读理解&#xff0c;此处不涉及详细的原理性解读。 一、什么是总线风暴 总线风暴&#xff0c;听着真是一个帅气的词语&#xff0c;但如果发生在你的系统上那就不是很美丽了&am…...

    2024/5/8 12:51:56
  8. NodeJs实战第三章之简单实现增删改查接口

    具体demo如下&#xff1a; const express require(express); const bodyParser require("body-parser") const app express(); const articles [{title:这是一篇文章}] const port process.env.PORT || 3000;app.use(bodyParser.json()); //支持编码为JSON的…...

    2024/4/21 8:52:36
  9. 抛砖引玉系列之gradle文件配置差异化打包

    在项目开发的过程中&#xff0c;总能碰到这样的需求&#xff0c;打包的时候发布两个相同版本的包&#xff0c;但是服务器地址不同&#xff0c;这时候就不想打两个包的时候只想运行一句打包命令打出两个服务器地址的包&#xff0c;这个时候就想到可以根据gradle配置打出差异化版…...

    2024/4/21 20:33:28
  10. RTSP网络摄像头网页无插件直播视频平台EasyNVR自定义标题和CopyRight步骤

    由于EasyNVR视频平台能够直接集成到其他的平台定制专属于自己企业的视频监控方案&#xff0c;所以很多项目团队都会选择变更EasyNVR的标题以及CopyRight&#xff0c;换成项目相关的内容&#xff0c;这个需求EasyNVR也是可以实现的&#xff0c;下面就来讲一下实现方法。 之前的方…...

    2024/5/4 21:57:17
  11. 解读华为云原生数据库设计原则,打破传统数据库上云瓶颈

    摘要&#xff1a;一个优秀的自研数据库产品应该要具备哪些特性呢&#xff1f;在云计算技术不断成熟的背景之下&#xff0c;云数据库开始崛起&#xff0c;并因为按需扩展、按需付费等优异特性获得中小企业及互联网客户的青睐。 虽然数据库上云是必然&#xff0c;但并不是万能的…...

    2024/4/28 23:57:44
  12. 搭档之家|独立面对人生的态度

    搭档之家&#xff1a;才知道一个人是常态 搭档之家也见过很多年轻人&#xff0c;在学校里&#xff0c;在家庭里&#xff0c;不知何为一个人。 一个人住宿是常态&#xff0c;一个人看病是常态&#xff0c;一个人吃饭是常态&#xff0c;一个人解决问题是常态&#xff0c;一个人是…...

    2024/4/15 9:11:30
  13. 下订单溢出模拟并发

    我用sql server 做了 一个模拟 一张订单表 一张产品表 逻辑是准备一个存储过程 产品数量>0就可以 往 订单表insert 一条记录 产品数量-- ; 这个 存储过程 没添加 事务 没指定 会话隔离级别 也没有 加锁 ALTER PROCEDURE [dbo].[pro_createorder_auto] errormsg n…...

    2024/4/27 14:41:20
  14. 记录java的多次线程

    线程的优先级 每一个 Java 线程都有一个优先级&#xff0c;这样有助于操作系统确定线程的调度顺序。 Java 线程的优先级是一个整数&#xff0c;其取值范围是 1 &#xff08;Thread.MIN_PRIORITY &#xff09; - 10 &#xff08;Thread.MAX_PRIORITY &#xff09;。 默认情况下…...

    2024/4/25 23:26:49
  15. 微信公众号链接自定义分享总结

    微信H5自定义分享页面总结 微信公众号中默认的分页页面很难看&#xff0c;默认的缩略图。工作需要将他自定义为想要的形式。 具体详情查看微信开发者文档&#xff1a;微信开发者文档 第一步、公众号需要配置 1、需要一个公众号的 appId、appSecret 只要有公众号&#xff0c;…...

    2024/4/24 18:46:50
  16. 面对疾风吧!io_uring 优化 nginx 实战演练

    作者&#xff1a;玖一&#xff08;云巅论剑&#xff09; 引言 io_uring是Linux内核在v5.1引入的一套异步IO接口&#xff0c;随着其迅速发展&#xff0c;现在的io_uring已经远远超过了纯IO的范畴。从Linux v5.3版本开始&#xff0c;io_uring陆续添加了网络编程相关的API&#…...

    2024/4/17 22:38:56
  17. Linux下处理时间同步相关问题汇总

    问题1 &#xff1a;时间同步时提示28 Sep 11:43:42 ntpdate[19730]: the NTP socket is in use, exiting 同步失败 您收到此错误消息的原因是由于 xntpd 已经绑定到了该 Socket。运行 ntpdate 时&#xff0c;它会首先进行广播&#xff0c;然后侦听端口 123。如果 xntpd 正在运…...

    2024/4/23 17:59:46
  18. 并查集——(三)C++ 使用 STL 的 map 实现查并集功能

    综述 我们接上一节&#xff0c;https://blog.csdn.net/justidle/article/details/108846236&#xff0c;继续讨论并查集问题。我们发现使用 C 数组实现并查集主要问题有以下几个&#xff1a; 1、元素中不能支持负数。因为 C 规定数组的下标不能是负数。 2、代码量相对比较大…...

    2024/5/6 1:07:53
  19. springboot+rabbitmq整合五种模式,所遇到的问题,搞了半天,结果就是因为声明的Binding中有个routingKey设置了null,最后我的绑定硬是没有绑定到rabbitmq中;

    由于笔者粗心大意在做Topic模式时&#xff0c;发现自己建立的Binding一直没有映射到RabbitMq中&#xff0c;明明都写了好了Queue、Exchange、Binding&#xff0c;结果在rabbitmq的管理界面没有绑定关系&#xff0c;抓狂&#xff0c;以下是自己的分析。 不废话直接上错误代码 /…...

    2024/4/26 5:01:15
  20. xcx-地图位置偏移解决

    //引入转换地图经纬度js var WSCoordinate require(./../../WSCoordinate.js); var QQMapWX require(../../qqmap-wx-jssdk.js),qqmap new QQMapWX({ key: O77BZ-2KXKO-7RIWS-SHI7I-LLSK7-OEBKZ });1.获取自动定位时&#xff1a;将获取的腾讯地图经纬度转换成百度地图经纬度…...

    2024/4/22 19:54:06

最新文章

  1. 在阿里云K8S容器中,部署websocket应用程序的总结

    一、背景 有一个websocket应用程序&#xff0c;使用spring boot框架开发&#xff0c;http端口号是6005&#xff0c;提供的是websocket服务&#xff0c;所以它还监听一个8889端口的tcp协议。 现在要把它部署到阿里云的k8s容器里&#xff0c;本文着重描述service层的配置。 因…...

    2024/5/9 4:40:33
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. Oracle备份和还原的几种方式

    1、使用数据泵方式 exp demo/demoorcl buffer1024 filed&#xff1a;\back.dmp fully demo&#xff1a;用户名、密码 buffer: 缓存大小 file: 具体的备份文件地址 full: 是否导出全部文件 ignore: 忽略错误&#xff0c;如果表已经存在&#xff0c;则也是覆盖 exp demo/de…...

    2024/5/8 15:37:29
  4. Java-运算符

    运算符 Java语言支持如下运算符&#xff1a; 算术运算符&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;%&#xff0c;&#xff0c;--复制运算符&#xff1a;关系运算符&#xff1a;>, <, >, <, , !instanceof逻辑运算符&#xff1a;&&…...

    2024/5/8 1:50:44
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/8 19:32:33
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/7 22:31:36
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/9 2:44:26
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/8 20:33:13
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/9 3:15:57
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/7 16:57:02
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/7 14:58:59
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/8 20:58:56
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/9 1:35:21
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/9 4:12:16
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/7 16:05:05
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/8 18:06:50
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/8 1:37:32
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/9 1:42:21
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/8 1:37:31
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/9 4:31:45
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/8 12:44:41
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/8 9:51:44
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/8 1:37:29
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/9 4:33:29
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57