浅谈 Linux 中的 core dump 分析方法

news2024/11/17 20:47:31

文章目录

  • 一、什么是 core dump
  • 二、发生 core dump 的原因
    • 1. 空指针或非法指针引起 core dump
    • 2. 数组越界或指针越界引起的 core dump
    • 3. 数据竞争导致 core dump
    • 4. 代码不规范
  • 三、core dump 分析方法
    • 1. 启用 core dump
    • 2. 触发 core dump
      • 2-1. 因空指针解引用而崩溃
      • 2-2. 通过 SIGSEGV 信号触发 core dump
    • 3. gdb 分析 core dump
  • 总结

在 Linux 系统开发领域中,core dump(核心转储)是一个不可或缺的工具,它为我们提供了在程序崩溃时分析程序状态的重要线索。当程序因为某种原因(如段错误非法指令等)异常终止时,Linux 系统会尝试将程序在内存中的映像、程序计数器、寄存器状态等信息写入到一个名为 core 的文件中,这个文件就是所谓的 core dump。

对于开发者而言,core dump 文件如同一块宝藏,其中蕴含着程序崩溃时的现场信息。通过对 core dump 文件的分析,我们可以了解到程序在崩溃时的内存布局、函数调用栈、变量值等重要信息,从而帮助我们快速定位问题原因,优化代码,提高程序的健壮性。

在本文中,我们将探讨 Linux 中 core dump 的分析方法。通过一些简单的案例来演示 core dump 分析的实际应用,帮助读者更好地理解和掌握这一技术。
在这里插入图片描述

一、什么是 core dump

核心转储(core dump),在汉语中有时戏称为吐核,是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。这种信息往往用于调试。

在 UNIX 系统中,常将“主内存称为核心(core),因为在使用半导体作为内存材料之前,便是使用核心(core)。而核心映像(core image)就是 “进程”(process)执行当时的内存内容。当进程发生错误或收到 “信号”(signal)而终止执行时,系统会将核心映像写入一个文件,以作为调试之用,这就是所谓的核心转储(core dump)。

有时程序并未经过彻底测试,这使得它在执行的时候一不小心就会找到破坏。这可能会导致核心转储(core dump)。幸好,现行的 UNIX 系统极少会面临这样的问题。即使遇到,程序员可以通过核心映像(core image)调试程序来找到错误原因。

——引用:核心转储_百度百科 (baidu.com)

可以这样去理解,core dump 是程序运行时在突然崩溃的那一刻的一个内存快照。操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下,会把进程此刻内存、寄存器状态、运行堆栈等信息转储保存在一个 core 文件里。这个 core 文件是二进制文件,可以使用 gdbelfdumpobjdump 或者 Windows 下的 windebug 进行打开此文件,并分析里面的具体内容,找出 core dump 的具体原因,并解决问题。

[!NOTE]

core 是在半导体作为内存材料前的线圈,当时用线圈当做内存材料,线圈叫做 core。用线圈做的内存叫做 core memory。故 core dump 也可称为 core memory dump,真是个充满历史味道的词。

在 Linux 系统下开发,时常会遇到程序突然崩溃了,且没有留下任何日志的情况,这时就可以查看 core 文件。从 core 文件中分析原因,通过 gdb 看出程序挂在哪里,分析前后的变量,找出问题的原因。

二、发生 core dump 的原因

C/C++ 程序员遇到的比较常见的一个问题,就是自己编写的代码, 在运行过程中出现了意想不到的 core dump。程序发生 core dump 的原因是多方面的,不同的 core dump 问题有着不同的解决办法。同时,不同的 core dump 问题解决的难易程度也存在很大的区别。有些在短短几秒钟内就可以定位问题,但是也有一些可能需要花费数天时间才能解决。这种问题是对软件开发人员的极大的挑战。笔者从事 C/C++ 语言的软件开发工作多年,前后解决了许多此类问题,久而久之积累了一定的经验,现把常见 core dump 总结一下。

1. 空指针或非法指针引起 core dump

空指针或非法指针(野指针、悬空指针)引起 core dump 是一种最常见的核心转储,大致可以有 3 种原因导致程序出现异常:

  1. 对空指针进行解引用等操作;

  2. 声明指针变量后未进行初始化,并直接进行操作,极大概率引发 core dump,此类未经初始化的指针,统称野指针

  3. 对某个指针,调用了 free 函数或者 delete 函数,该指针指向的空间已经被释放,但未将该指针重新指向 NULL,此类指针成为悬空指针。对悬空指针再次操作,也会引发 core dump;

此类问题通常是代码编写时的疏漏造成的,属于低级 bug,也比较容易解决的问题。Linux 平台常用的 core dump 文件分析工具是 gdb,调试一下产生的 core 文件,对照代码定位问题出现的原因,可以轻松解决问题。

2. 数组越界或指针越界引起的 core dump

提到这个,笔者不由得想起互联网大厂百度的一道 C 语言面试题,如下代码:

#include <stdio.h>

int main()
{
	int i;
	int array[6];

	for (i = 0; i < 8; i++) {
		array[i] = 0;
		printf("Grayson Zheng\n");
	}

	return 0;
}

问:以上代码中的 printf 函数会执行多少次?

这个问题的答案在不同操作系统下有不同的答案,当下只讨论 Linux 系统的结果,执行该程序,结果如下:

在这里插入图片描述

可以看出,在打印了 8 次之后,程序结束,但这并不是一次正常的结束,而是一次 core dump。不难看出这是数组越界导致的内存踩踏,数组定义了 6 个元素,遍历完 6 个元素之后,还对数组之外的内存进行了操作,从而引发了这次的 core dump。

这种情况还相对简单,而指针越界引发的 core dump,有的是就比较简单,有的就属于一种隐藏比较深的 core dump 了。遇到这种问题时,在调试 core 文件,尽管也能定位到代码行,但是有可能呗定位到的那行代码本身并没有什么问题,它只是一个 “被陷害者”。

根据经验,这种 core dump 问题很可能是其他代码处理过程中的内存越界造成的(亲身经历:一个指针越界导致内存踩踏,让 7.5 万台机器拆包重流,经济损失估计超过 40 w。当然,我不是那个写 bug 的人,哈哈),通常由以下两个原因引起:

  1. 假如有以下三个全局变量:

    int  global_vsrisble_a;
    char global_vsrisble_b;
    char global_vsrisble_c;
    

    在不同操作系统中,这个三个全局变量在内存的位置可能不一样,以 Ubuntu 为例,三个全局变量的内存位置分布如下图所示:

    在这里插入图片描述

    假设在某些做了如下代码所作的事:

    #include <stdio.h>
    
    int  global_vsrisble_a = 0x11223344;
    char global_vsrisble_b = 0x55;
    char global_vsrisble_c = 0x66;
    
    int main()
    {
        printf("%p = 0x%X\n%p = 0x%X\n%p = 0x%X\n", &global_vsrisble_a,
               global_vsrisble_a, &global_vsrisble_b, global_vsrisble_b,
               &global_vsrisble_c, global_vsrisble_c);
    
        char *p_1 = (char *)(&global_vsrisble_a);
        p_1 += 2;
        int *p_2 = (int *)p_1;
        *p_2 = 0x09ABCDEF;
    
        printf("%p = 0x%X\n%p = 0x%X\n%p = 0x%X\n", &global_vsrisble_a,
               global_vsrisble_a, &global_vsrisble_b, global_vsrisble_b,
               &global_vsrisble_c, global_vsrisble_c);
    
        return 0;
    }
    

    [!CAUTION]

    以上代码只是为了示范,现实情况并不可能如此。

    执行代码后如下:

    0x6447cc49a010 = 0x11223344
    0x6447cc49a014 = 0x55
    0x6447cc49a015 = 0x66
    0x6447cc49a010 = 0xCDEF3344
    0x6447cc49a014 = 0xFFFFFFAB
    0x6447cc49a015 = 0x9
    

    从执行结果来看,global_vsrisble_bglobal_vsrisble_c 的值被破环。

    举这个例子是为了说明,如果通过调试工具定位到是因为 global_vsrisble_b 的值被破坏了,很可能不是操作 global_vsrisble_b 的代码有问题,而是操作 global_vsrisble_a 或者 global_vsrisble_c 失误,导致了 global_vsrisble_b 的出错,进而引发 core dump。

  2. 内存变量的值莫名其妙出现奇怪的值。跟上面的情况有点类似,也是因为有些变量相邻问题被覆盖原有的值。例如,执行了 memcpystrcpy 等函数(string.h 涉及到复制功能的函数,在复制过程中是不会检查是否有越界的风险的)引起的 core dump。对于这类问题,肯定是代码走到了某个特殊的逻辑里面,代码处理缺少必要的保护而引起的。

    此类 core dump 可以通过复现 bug,对比前后两次的 core 文件,找出内存变量存在的某种共性特征,根据这个特征来分析解决问题。

    [!NOTE]

    曾经在工作中遇到过一个 core bump,起因是对一段未初始化的缓冲存储区做字符串搜索(搜索并不会引发 core dump)。但是代码流程走了很长一段之后,对一个与缓冲存储区相邻的变量执行了操作,导致了 core dump。

3. 数据竞争导致 core dump

多线程访问全局变量,如果不进行适当的同步保护,确实可能导致内存值异常,从而引发不可预测的行为,甚至可能导致程序崩溃并生成核心转储文件(core dump)。这种问题通常称为 “数据竞争” 或 “竞态条件”(race condition)。

竞态条件是指两个或多个线程同时访问共享数据,并且至少有一个线程在修改数据时未进行适当的同步。这可能导致以下问题:

  1. 数据不一致:多个线程读取和修改全局变量时,可能会导致数据处于不一致的状态。
  2. 程序崩溃:未同步的访问可能导致非法的内存访问,从而引发段错误(segmentation fault),导致程序崩溃并生成核心转储文件。

4. 代码不规范

初学者有时候编译一个程序,出现了一整页的编译错误,其实这种情况也不用担心,很可能就是某一行代码多了几个字符,当把这些代码删去再编译,几百个编译错误全都消失了。

有些时候,程序发生 core dump 的根本原因还是程序员自己进行程序设计时的编码失误造成的,这种代码失误绝大多数都是因为没有严格遵守相应的代码编写规范(比如用 0 做为除数等)。所以,要从根本上杜绝或者减少程序 core dump 的发生,还是要从严格遵守代码编写规范来做起。

三、core dump 分析方法

1. 启用 core dump

默认情况下,程序运行崩溃导致 core dump,是不会生成 core 文件的,因为系统的 RLIMIT_CORE(核心文件大小)资源限制,默认情况下设置为 0。

使用 ulimit -c 命令可以查看 core 文件的大小,其中 -c 的含义是 core file size,单位是 blocks 也就是 KB 的意思。ulimit -c 命令后面可以写整数,表示生成写入值大小的 core 文件。如果使用 ulimit -c unlimited 设置无限大,则任意情况下都会产生 core 文件。

以下命令可在用户进程触发信号时启用 core dump 生成,并使用合理的名称将核心文件位置设置为 /tmp/。请注意,这些设置不会永久存储。

ulimit -c unlimited
echo 1 > /proc/sys/kernel/core_uses_pid
echo "/tmp/core-%e-%s-%u-%g-%p-%t" > /proc/sys/kernel/core_pattern

[!IMPORTANT]

后面两条命令在运行时,即使是加了 sudo 执行,也可能会被提示权限不足。这可能是由于 shell 的重定向在命令前已经处理完成,因此重定向操作并没有被提升到超级用户权限,这就导致了 “Permission denied” 的错误。可以通过以下命令来解决这个问题:

echo 1 | sudo tee /proc/sys/kernel/core_uses_pid
echo "/tmp/core-%e-%s-%u-%g-%p-%t" | sudo tee /proc/sys/kernel/core_pattern

顺便解释一下 "/tmp/core-%e-%s-%u-%g-%p-%t" 的各个参数的含义:

  • %e:导致 core dump 的程序的可执行文件名。
  • %s:导致 core dump 的信号编号。
  • %u:导致 core dump 的程序的实际用户 ID。
  • %g:导致 core dump 的程序的实际组 ID。
  • %p:导致 core dump 的程序的进程 ID。
  • %t: core dump 发生时的时间戳(自 epoch 时间以来的秒数)。

因此,/tmp/core-%e-%s-%u-%g-%p-%t 会生成包含如下信息的 core 文件:

/tmp/core-<executable>-<signal>-<uid>-<gid>-<pid>-<timestamp>

举个例子,如果一个进程名为 my_program,用户 ID 为 1000,组 ID 为 1000,进程 ID 为 12345,并且在 1617701234 时间点崩溃于信号 11,则生成的 core 文件名将是:

/tmp/core-my_program-11-1000-1000-12345-1617701234

2. 触发 core dump

我们使用两个简单的 C 程序作为示例。

2-1. 因空指针解引用而崩溃

文件名为 example.c

#include <stdio.h>

void func()
{
    int *p = NULL;
    *p = 13;
}

int main()
{
    func();
    return 0;
}

编译并运行程序:

gcc -g -o example example.c
./example

运行程序时后,会在 /tmp/ 文件夹下生成一个 core 文件。

在这里插入图片描述

2-2. 通过 SIGSEGV 信号触发 core dump

文件名为 example2.c

#include <stdio.h>
#include <unistd.h>

int global_num;

int main()
{
	while(1) {
		printf("global_num = %d\n", global_num++);
		sleep(1);
	}

	return 0;
}

编译并运行程序:

gcc -g -o example2 example2.c
./example2

运行程序时后,在另一个终端查找进程的 PID,并用 kill -11 加上 PID,向进程发送段错误信号,结束掉进程。之后会在 /tmp/ 文件夹下生成一个 core 文件。

在这里插入图片描述

3. gdb 分析 core dump

两个例子都是段错误导致的 core dump,所以用 gdb 调试的方法也是一样的,命令格式如下:

gdb <program_name> <core_dump_file>

比如先调试第一个例子的 core 文件,则输入 gdb example,再加上 core 文件名,命令如下(建议先提前复制 core 文件名,不知道为什么,按 Tab 键不给补齐):

gdb example /tmp/core-example-11-1000-1000-88496-1719910934

随后可以看到,gdb 提示在代码第 6 行的地方出现了段错误,如下图:

在这里插入图片描述

如果函数关系调用关系很复杂,可以用 bt 命令(全称 backtrace,堆栈的意思)查看调用堆栈(where 命令也有同样功能),如下图可知是在调用 func 函数时产生的段错误,可用 list 命令查看,具体就是 list 加函数名,如下图。找到提示错误的那一行代码,print 命令可以打出 p 的值,由下图可知,p 是空指针,不能进行解引用操作。

输入 quitexit 可以退出 gdb。

在这里插入图片描述

第二个例子,也是同样用 gdb 打开 core 文件:

gdb example2 /tmp/core-example2-11-1000-1000-88552-1719911473

执行结果如下图:

在这里插入图片描述

虽然这个段错误是因为我们人为地发送了 SIGSEGV 信号,导致了程序地段错误,而在打开 core 文件后,可以看出在执行 __GI___clock_nanosleep 函数时,遇到了段错误。

[!NOTE]

通常情况下,分析 core dump 问题,除了 core 文件之外,还会结合程序的 log 信息和系统的 log 信息(包括 kernel log、systemd log 等)一起分析。

当然人为故意制造出来的 core dump,有时候是分析不出来的。所以这个例子的作用在于分析的过程,也顺便告诉大家,不是所有的 core dump 都可以分析出具体原因。

如果我们不事先知道是由 SIGSEGV 信号导致段错误的,首先要用 bt 命令找到函数的调用关系链:

在这里插入图片描述

由上图可知,先是在 main 函数调用了 __sleep 函数,接着 __sleep 函数调用了 __GI___nanosleep 函数,__GI___nanosleep 函数调用了 __GI___clock_nanosleep 函数,到这里,执行到了 __GI___clock_nanosleep 函数的第 78 行时,发生了段错误,使程序崩溃。

此时,我们是没办法通过 list 命令去找出问题的,因为栈区的那三个函数是封装后的库函数,根本看不到源码:

在这里插入图片描述

在输入 bt 命令查看堆栈情况时,有出现了两个变量,分别是 reqrem。使用过nanosleep 函数的小伙伴可能会很眼熟这两个变量,因为这个两个变量是 nanosleep 函数的形参,原型是 int nanosleep(const struct timespec *req, struct timespec *rem)

print 命令打印出两个变量的地址:

在这里插入图片描述

使用 info registers 命令查看寄存器状态,检查程序在崩溃时的上下文:

在这里插入图片描述

从寄存器状态来看,没有明显的错误迹象,函数的栈帧空间没什么问题,形参的位置和值也没什么问题,所有值看起来都在正常范围内。

当下是没办法直接了当的判断为人为干预造成 core dump,如果此时想到了信号会引发段错误,可以用 info signals 命令查看信号情况:

在这里插入图片描述

info signals 的输出中可以看出,SIGSEGV(Segmentation fault)信号是设置为在程序接收到该信号时停止执行并打印信息的。也就说,可以人为地使用 kill -11 发送了 SIGSEGV 信号来终止程序并生成 core dump。

总结

分析 core dump 的具体原因不可能仅凭两个案例就学会,本文只是提供一个基本的排除思路和方法。通过查看调用堆栈、源代码和变量的值,可以逐步确定程序崩溃的原因。通过向程序发送 SIGSEGV 信号来生成 core 文件是一个有效的调试手段。通过 gdb,可以详细分析程序在崩溃时的状态,并确定具体的崩溃原因。确保在信号触发时,检查程序的变量和内存状态,能够帮助你更好地理解和解决程序中的问题。

之后如果遇到一个实际工作中产生的 core dump,且具有学习价值,我一定会总结这个分析过程,并输出成文档的形式,分享给大家,共勉,respect~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1891817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

S272钡铼技术4G无线RTU支持多路DIN输入和模拟量转换至4G网络

钡铼第四代RTU S272是一款先进的工业级4G远程遥测终端&#xff0c;为各种远程工业数据采集和控制系统提供了高效解决方案。结合了现代通信技术和多功能的输入输出接口&#xff0c;S272不仅支持多路数字量和模拟量输入&#xff0c;还具备灵活的扩展性和强大的控制功能&#xff0…

如何利用小猪APP分发轻松将网页封装成APP

什么是网页封装APP&#xff1f; 假设你有一个非常棒的网站&#xff0c;但你希望用户能更方便地在手机上访问它。你可能会考虑将该网页封装成一个APP。封装APP其实就是将网页内容打包成一个移动应用&#xff0c;这样用户可以像使用其他APP一样方便地使用你的服务。 APP分发www…

大华设备接入GB28181/GAT1400视频汇聚管理平台EasyCVR安防监控系统的具体操作步骤

智慧城市/视频汇聚/安防监控平台EasyCVR兼容性强&#xff0c;支持多协议接入&#xff0c;包括国标GB/T 28181协议、GA/T 1400协议、部标JT808协议、RTMP、RTSP/Onvif协议、海康Ehome、海康SDK、大华SDK、华为SDK、宇视SDK、乐橙SDK、萤石云SDK等&#xff0c;并能对外分发RTMP、…

动物检测yolo格式数据集(水牛 、大象 、犀牛 、斑马四类)

动物检测数据集 1、下载地址&#xff1a; https://download.csdn.net/download/qq_15060477/89512588?spm1001.2101.3001.9500 2、数据集介绍 本数据集含有四种动物可以检测&#xff0c;分别是水牛 、大象 、犀牛 、斑马四类&#xff0c;数据集格式为yolo格式&#xff0c;…

java进行音视频的拆分和拼接

一、下载ffmpeg并安装 官网地址https://ffmpeg.org/download.html 载后解压缩如下 D:\google-download\ffmpeg-2024-01-28-git-e0da916b8f-essentials_build\bin>ls ffmpeg.exe ffplay.exe ffprobe.exe 展示三个exe可执行文件 配置环境变量 配置后直接在cmd输入ffmpeg…

首家!腾讯云数据万象通过中国信通院智能存储专项测试

2024年6月19日&#xff0c;由中国通信标准化协会主办&#xff0c;中国通信标准化协会大数据技术标准推进委员会(CCSA TC601)承办的首届“数据智能大会”在京隆重召开。腾讯云存储受邀出席了活动&#xff0c;大会中“可信数据智能”系列评估测试结果正式颁布&#xff0c;经过严苛…

AI是在帮助开发者还是取代他们?

一&#xff1a;介绍 生成式人工智能&#xff08;AIGC&#xff09;在软件开发领域的应用确实为开发者带来了很多便利和效率提升。AI工具可以通过代码生成、错误检测、自动化测试等功能&#xff0c;帮助开发者更快速地开发和优化软件&#xff0c;减少重复性工作&#xff0c;提高…

商标的近似分辩,商标起名称时注意!

曾有过网友发来商标名称&#xff0c;普推知商标老杨说有近似&#xff0c;然后网友起过新名称还是存有近似&#xff0c;或者加字&#xff0c;后面加的通用词&#xff0c;与先有商标名称也是近似。 “良信健康”这个名称健康是行业通用词&#xff0c;加成健康后变成四个字&#x…

C++ 语法

一、头文件与源文件 头文件用于声明函数,类似于java中service层的接口; 源文件用于实现头文件函数,相当于java中serviceImpl层的实现类; 定义接口 实现接口 使用接口 二、指针概述 定义与使用 定义一个指针p用于存a变量的内存地址,即指针就是地址; 解引用可以获取或修改…

【C++】 解决 C++ 语言报错:Double Free or Corruption

文章目录 引言 双重释放或内存破坏&#xff08;Double Free or Corruption&#xff09;是 C 编程中常见且严重的内存管理问题。当程序尝试多次释放同一块内存或对已经释放的内存进行操作时&#xff0c;就会导致双重释放或内存破坏错误。这种错误不仅会导致程序崩溃&#xff0c…

提升Android Studio开发体验:使用Kelp插件实现颜色和图标预览

提升Android Studio开发体验&#xff1a;使用Kelp插件实现颜色和图标预览 在Android开发中&#xff0c;自动补全功能对于提高开发效率至关重要。然而&#xff0c;默认的Android Studio并不能预览颜色和图标&#xff0c;这使得开发者在选择资源时常常感到困惑。本文将介绍如何使…

sql优化-单表优化

文章目录 0、索引优化原则1、在docker内部连接mysql2、数据准备3、创建表 dept 和 emp4、插入50万数据到 emp 表中4.1、创建函数4.2、存储过程4.3、调用存储过程 5、查找姓名以"abc"开头的员工信息5.1、执行计划 select * from emp where name like abc%;5.2、sql优化…

容器部署rabbitmq集群迁移

1、场景&#xff1a; 因业务需要&#xff0c;要求把rabbitmq-A集群上的数据迁移到rabbitmq-B集群上&#xff0c;rabbitmq的数据包括元数据&#xff08;RabbitMQ用户、vhost、队列、交换和绑定&#xff09;和消息数据&#xff0c;而消息数据存储在单独的消息存储库中。 2、迁移要…

大模型备案全网最详细流程【附附件】

本文要点&#xff1a;大模型备案最详细说明&#xff0c;大模型备案条件有哪些&#xff0c;《算法安全自评估报告》模板&#xff0c;大模型算法备案&#xff0c;大模型上线备案&#xff0c;生成式人工智能(大语言模型)安全评估要点&#xff0c;网信办大模型备案。 大模型备案安…

火山云存储TOS前端预签名上传文件

使用POSTMAN中的PUT方法&#xff0c;Body选择binary, 然后添加文件&#xff0c;可以上传图片&#xff0c;视频&#xff0c;音频等

大模型概述-定义/分类/训练/应用

大模型概述 随着时代的发展, 大模型各个领域的应用正在不断扩大. 本文尽力梳理各种材料, 将从概念定义, 类型分类, 训练以及应用等方面对大模型进行一个简要的概述. 如果你想了解大模型但是却缺乏基础的知识或者觉得无从下手, 那么阅读该文章可能对你有所帮助. 如果想了解更多…

SQL Server数据库的组成

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 对于数据库的概念&#xff0c;没有一个完全固定的定义&#xff0c;随着数据库历史的发展&#xff0c;定义的内容也有很大的差异&#xff0c;其中一种比较普遍的观点认为&#xff0c;…

Halcon OCR字符识别(极坐标转换,字符识别)

Halcon OCR字符识别&#xff08;极坐标转换&#xff0c;字符识别&#xff09; 代码 * 1.加载图片 *************************************************** dev_close_window () read_image (Image, ./img) get_image_size (Image, Width, Height) dev_get_window (WindowHandle…

153. 寻找旋转排序数组中的最小值(中等)

153. 寻找旋转排序数组中的最小值 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;153. 寻找旋转排序数组中的最小值 2.详细题解 如果不考虑 O ( l o g n ) O(log n) O(logn)的时间复杂度&#xff0c;直接 O ( n ) O(n) O(n)时间复杂…