gdb

news/2024/10/6 20:52:18/文章来源:https://www.cnblogs.com/jaqennnnn/p/18449364

gdb控制程序行为

  • 环境变量

    (gdb) set environment VAR=value

多线程

相关命令

  • 查看线程列表:在 gdb 中可以使用以下命令查看当前所有线程的列表:

    (gdb) info threads
    

    这将列出所有线程及其状态,包括它们的 LWP ID 和线程标识符。

  • 切换到特定线程:如果你想切换调试上下文到某个特定的线程,可以使用:

    (gdb) thread <thread_number>
    

    其中 <thread_number>gdb 为线程分配的编号(可以从 info threads 命令中获取)。

  • 查看所有线程的调用栈(批量查看每个线程的栈帧):

    (gdb) thread apply all bt
    

    这会列出所有线程的调用栈信息,方便你快速发现哪个线程挂掉或崩溃。

使用 pstack 工具查看线程栈
如果你只想快速查看某个进程的所有线程的栈,而不需要全面调试,可以使用 pstack 工具(需要安装)。它会显示所有线程的调用栈(包括 LWP ID)。

通过 pstop 查看线程状态

gdb 之外,你也可以通过系统命令查看线程的状态:

  • 使用 ps 查看线程状态

    bash

    Copy

    ps -eLf | grep <PID>
    

    该命令会列出指定进程的所有线程,并显示线程的状态、PID、LWP 等信息。你可以通过 S 列查看线程的状态:

    • R:正在运行。
    • S:休眠中。
    • D:不可中断的睡眠(通常是 I/O 操作)。
    • Z:僵尸线程。
  • 使用 top 查看线程状态

    启动 top 命令并按 H 键,可以查看某个进程的所有线程及其状态。

    bash

    Copy

    top -H -p <PID>
    

    这将显示该进程的所有线程及其 CPU 使用率、状态等信息。

总结

  • 使用 gdb 中的 info threads 命令可以查看所有线程的状态。
  • 使用 thread <id> 切换到特定线程,使用 backtrace 查看其调用栈。
  • 使用 thread apply all bt 可以一次性查看所有线程的调用栈,快速定位问题线程。
  • 通过调用栈可以识别线程是否因段错误、死锁、或其他系统调用而挂起。
  • 使用系统工具如 pstop 也可以查看线程的状态,辅助判断哪个线程可能挂掉了。

信号

SIGABRT 信号通常在以下情景下触发:

  • 显式调用 abort()
  • 未通过 assert 检查
  • 内存管理错误(如非法释放内存或越界访问)。
  • 未捕获的异常(在 C++ 中,抛出异常但未捕获时会调用 std::terminate(),进而触发 SIGABRT)。
  • 手动发送 SIGABRT 信号

情境问题

如何知道一个文件被哪些进程的哪些函数读取过?

要知道一个文件被哪些进程的哪些函数读取过,这个问题涉及两部分:

  1. 哪些进程读取了该文件:这是系统级别的问题,通过跟踪文件系统调用可以获取相关信息。
  2. 哪些函数读取了该文件:这是进程内部的问题,需要了解进程内的调用栈。

要实现这两部分的监控,通常需要结合系统工具和调试工具来完成。以下是几种常见的方式:

1. 使用 strace 跟踪文件访问系统调用

strace 是 Linux 系统中的一个工具,可以跟踪进程的系统调用,包括 open(), read(), write() 等文件操作的系统调用。通过 strace,你可以知道某个文件被哪些进程读取过。

步骤:

  1. 跟踪所有进程对文件的访问

    你可以通过 strace 跟踪所有进程的文件系统调用,并过滤出对特定文件的访问。例如,假设你想跟踪 /path/to/file 文件的访问,可以使用以下命令:

    sudo strace -f -e trace=open,read,write -p $(pgrep -d, .) 2>&1 | grep "/path/to/file"
    

    解释:

    • -f:跟踪子进程。
    • -e trace=open,read,write:只跟踪 open(), read(), write() 系统调用。
    • -p $(pgrep -d, .):跟踪所有进程(pgrep -d, . 会输出所有进程的 PID)。
  2. 跟踪特定进程的文件访问

    如果你知道某个进程的 PID,你可以使用 strace 直接跟踪该进程:

    sudo strace -f -e trace=open,read,write -p <PID>
    

    这将显示该进程打开、读取、写入的文件。你可以结合 grep 过滤出你感兴趣的文件。

  3. 输出示例

    假设有个进程在读取 /path/to/filestrace 的输出可能如下:

    open("/path/to/file", O_RDONLY) = 3
    read(3, "file contents...", 1024) = 1024
    

    这表明进程通过文件描述符 3 打开了文件 /path/to/file 并读取了 1024 字节的内容。

总结

  • strace:可以用来跟踪进程对文件的系统调用,知道哪些进程访问了文件。
  • lsof:可以实时查看哪些进程打开了文件。
  • auditd:可以对文件访问进行审计,记录哪些进程访问了文件。
  • gdbptrace:可以用来跟踪进程内部的函数调用栈,了解哪些函数读取了文件。
  • LD_PRELOAD:可以通过插桩方式重载文件操作函数,捕捉调用栈信息。

通过结合这些工具,你可以追踪文件访问的进程和函数调用栈。

使用 lsof 查看文件被哪些进程打开

lsof 是一个用于列出打开文件的工具。你可以使用它来查看某个文件当前被哪些进程打开。

步骤:

lsof /path/to/file

这将输出所有当前打开该文件的进程信息,包括进程 ID、进程名称、打开的文件描述符等。例如:

COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
cat      1234  user    3r   REG  8,1    1234     5678 /path/to/file

这里,cat 进程(PID 1234)以只读模式(3r)打开了 /path/to/file

3. 使用 auditd 进行文件访问审计

auditd 是 Linux 的审计框架,可以对文件访问进行详细的日志记录,包括进程访问文件的情况。

步骤:

  1. 安装 auditd

    如果未安装 auditd,可以使用以下命令安装:

    sudo apt install auditd
    
  2. 添加审计规则

    使用 auditctl 添加审计规则,跟踪某个文件的读取:

    sudo auditctl -w /path/to/file -p r -k file_read
    

    解释:

    • -w /path/to/file:监控文件 /path/to/file
    • -p r:监控读取操作。
    • -k file_read:为此规则设置一个标识符 file_read
  3. 查看审计日志

    审计日志会存储在 /var/log/audit/audit.log 文件中。你可以使用 ausearch 命令查看与文件访问相关的日志:

    sudo ausearch -k file_read
    

    这将显示哪些进程读取了 /path/to/file,包括详细的进程信息。

4. 使用 gdbptrace 获取函数调用栈

straceauditd 可以告诉你哪些进程访问了某个文件,但它们无法告诉你进程内部的函数调用栈。如果你想知道进程内部的哪些函数读取了该文件,则需要使用调试器(如 gdb)或 ptrace 进行更深入的分析。

步骤:

  1. 使用 gdb 附加到进程

    假设你已经知道某个进程(PID 为 <PID>)在访问文件,你可以使用 gdb 附加到该进程并设置断点在 open(), read(), 或 fopen() 等文件操作函数上:

    gdb -p <PID>
    
  2. 设置断点

    gdb 中设置断点,例如你想捕捉 open() 系统调用:

    (gdb) break open
    

    如果使用 C 标准库的 fopen() 函数:

    (gdb) break fopen
    
  3. 继续执行并捕捉调用栈

    继续执行进程,直到遇到断点:

    (gdb) continue
    

    当进程命中断点时,你可以使用 backtrace 命令查看当前的函数调用栈:

    (gdb) backtrace
    

    这将显示当前函数及其父函数,帮助你确定哪些函数调用了 open()read()

  4. 输出示例

    假设调用栈如下:

    #0  open (filename="/path/to/file", flags=O_RDONLY) at open.c:34
    #1  0x00007ffff7abc123 in read_file() at file_reader.c:54
    #2  0x00007ffff7abc456 in main() at main.c:12
    

    这表示 main() 调用了 read_file(),而 read_file() 又调用了 open() 打开了 /path/to/file

5. 自定义动态库插桩 (LD_PRELOAD)

可以通过插桩(interpose)标准库函数 open, read, fopen 等,来记录下文件访问的详细信息。你可以编写一个自定义的动态库,重载这些函数,在函数被调用时记录调用栈或其他信息。

步骤:

  1. 编写一个 C 文件,实现你要重载的函数:

    #define _GNU_SOURCE
    #include <dlfcn.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <execinfo.h>typedef int (*orig_open_f_type)(const char *pathname, int flags);int open(const char *pathname, int flags) {// 打印调用栈void *buffer[10];int nptrs = backtrace(buffer, 10);backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO);// 调用原始的 open 函数orig_open_f_type orig_open;orig_open = (orig_open_f_type)dlsym(RTLD_NEXT, "open");return orig_open(pathname, flags);
    }
    
  2. 编译成共享库:

    gcc -shared -fPIC -o myopen.so myopen.c -ldl
    
  3. 使用 LD_PRELOAD 运行目标程序:

    LD_PRELOAD=./myopen.so ./your_program
    

    这样,每次 open 函数被调用时,你的自定义函数会被调用,并打印出调用栈。

gdb假设没有加载符号表 是否能断点一些特定函数呢?

在 GDB 中,如果程序没有加载符号表(例如,编译时未使用 -g 选项),调试器将无法识别函数名或变量名。这是因为符号表包含了可执行文件中函数的名称、变量的名称及其对应的内存地址等调试信息。没有符号表的情况下,GDB 只能看到程序的机器代码和内存地址,而无法直接断点到特定的函数名。

然而,即使没有符号表,仍然有一些方法可以对特定的函数或代码位置设置断点,具体方法取决于情况:

1. 使用内存地址设置断点

如果你知道目标函数的内存地址,你可以直接在该地址设置断点。即使没有符号表,GDB 仍然可以在指定的内存地址处暂停程序执行。

步骤:

  1. 启动 GDB 并加载程序:

    gdb ./your_program
    
  2. 运行程序或使用 start 命令让程序执行到某个位置。

  3. 如果你知道目标函数的地址(例如,通过反汇编工具或 nm 命令获取),你可以直接在该地址设置断点。例如:

    break *0x4005d0
    

    这里 0x4005d0 是目标函数的内存地址。* 表示在该地址处设置断点。

  4. 运行程序,GDB 会在该地址处暂停。

如何获取函数地址:

  • 你可以使用 nmobjdump 等工具来列出程序中的函数地址。例如:

    nm ./your_program | grep some_function
    

    如果存在 some_function,它会显示类似以下的输出:

    00000000004005d0 T some_function
    

    其中 0x4005d0some_function 的地址。

  • 另一种方法是使用 objdump 来反汇编可执行文件并找到目标函数:

    objdump -d ./your_program | less
    

    然后在反汇编输出中查找你感兴趣的函数地址。

2. 使用汇编指令设置断点

如果你知道某个函数的汇编指令或知道特定的汇编指令模式(如函数的入口处通常是 pushmov 指令),你可以通过反汇编来找到这个函数的地址,并在该地址设置断点。

步骤:

  1. 反汇编程序的入口点或特定代码段:

    disassemble main
    

    如果没有符号表,GDB 也可以反汇编出汇编指令(即使没有函数名),例如:

    disassemble 0x4005d0
    
  2. 找到感兴趣的汇编指令后,设置断点:

    break *0x4005d0
    

3. 使用 GDB 的自动分析功能

GDB 有一些自动分析功能,即使没有符号表,它也可以尝试自动获取某些函数的地址。例如,info functions 可以列出程序中找到的所有函数地址(尽管没有符号名)。

info functions

这将列出程序中的所有已知函数,即使没有符号表,它也可能找到一些与标准库函数相关的符号。

4. 使用共享库中函数的符号

如果你调试的程序动态链接了共享库,即使主程序没有符号表,GDB 仍然可以识别共享库中的符号。例如,如果程序使用了标准 C 库函数 printf,你可以使用以下命令为 printf 设置断点:

break printf

因为共享库通常会加载自己的符号表,因此 GDB 可以识别这些库中的函数。

5. 使用条件断点

如果你不知道函数的内存地址,也没有符号表,但你知道某个特定位置的条件(比如某个寄存器的值或内存内容),你可以使用条件断点来捕捉该条件。例如,如果你知道某个函数的返回地址或特定的寄存器值,可以这样设置断点:

break *0x4005d0 if $rax == 5

这表示在地址 0x4005d0 处设置断点,但仅当寄存器 rax 的值为 5 时才触发断点。

小结

  • 没有符号表:GDB 无法直接通过函数名设置断点,但你可以使用目标函数的内存地址来设置断点。
  • 获取地址:可以通过工具如 nm, objdump,或者使用 GDB 的反汇编功能来获取函数的入口地址。
  • 共享库函数:即使没有符号表,动态链接库中的符号仍然可以被识别,允许为标准库函数设置断点。

例如,通过命令 break *<address>,你可以在任何特定的内存地址处设置断点。

假如一个线程因为抛了一个异常终止了 如何能知道哪个函数抛异常呢?在gdb中

当一个线程因为抛出异常而终止时,你可以使用 GDB 来调试并确定哪个函数抛出了异常。GDB 提供了多种工具来捕获异常和检查堆栈,从而帮助你找出异常的来源。

步骤概述:

  1. 设置断点捕获异常抛出
  2. 使用回溯(backtrace)查看调用栈,找出哪个函数抛出了异常。

1. 在 GDB 中捕获异常抛出

GDB 提供了一个特殊的命令可以捕获 C++ 异常抛出。当有异常抛出时,程序会暂停,这样你就可以在抛出异常的时刻检查调用栈。

你可以使用以下命令捕获所有 C++ 异常的抛出:

catch throw

这个命令会告诉 GDB,在异常被抛出的时候暂停程序的执行。

捕获异常的具体步骤:

  1. 启动 GDB,并加载你的程序:

    gdb ./your_program
    
  2. 设置捕捉异常的断点:

    catch throw
    
  3. 运行程序:

    run
    
  4. 当程序抛出异常时,GDB 会暂停,并显示类似以下的信息:

    Catchpoint 1 (throw)
    

2. 查看调用栈(Backtrace)

当程序由于抛出异常而暂停时,你可以使用 backtrace(或简写 bt)命令查看调用栈,找出哪个函数抛出了异常。

backtrace

这将显示当前的调用栈,列出从最底层到最顶层的函数调用顺序。通过这个调用栈,你可以找到抛出异常的函数。

示例:

假设你运行了程序并且捕捉到了异常抛出:

(gdb) catch throw
Catchpoint 1 (throw)
(gdb) run
Starting program: ./your_program
Catchpoint 1 (throw), __cxa_throw () at /build/glibc-23423....

现在你可以使用 backtrace 来查看调用栈:

(gdb) backtrace
#0  __cxa_throw () at /build/glibc-23423....
#1  0x00000000004015ae in some_function() at main.cpp:10
#2  0x0000000000401234 in main () at main.cpp:20

如上所示,some_function() 函数在 main.cpp 第 10 行抛出了异常。

3. 捕获异常处理开始(可选)

除了捕获异常的抛出,你还可以捕获异常的处理过程(即捕获 catch 处理异常的时刻),方法是使用:

catch catch

这会在异常被捕获处理的时候暂停程序。如果你想调试异常处理过程,这个命令非常有用。

4. 其他有用的 GDB 命令

  • info threads:查看所有线程的状态。如果你调试的是多线程程序,可以使用这个命令来查看线程情况。
  • thread apply all bt:如果程序有多个线程,这个命令可以显示所有线程的调用栈,帮助你确定哪个线程抛出了异常。

5. 使用调试符号

为了确保你能够看到函数名称、行号等详细信息,建议在编译程序时启用调试符号(使用 -g 选项)。例如:

g++ -g -o your_program your_program.cpp

如果没有调试符号,GDB 可能只会显示内存地址,而不会显示函数名称和代码行号。

6. 通过反汇编查看异常(无调试符号时)

如果你调试的程序没有调试符号,调用栈可能只会显示内存地址而不是函数名。在这种情况下,你可以使用 GDB 的 disassemble 命令来查看抛出异常的汇编代码,并手动确定抛出异常的位置。

例如:

disassemble 0x4005d0

这会显示内存地址 0x4005d0 处的汇编指令。你可以通过反汇编和代码分析,推测哪个函数抛出了异常。

总结

  1. 捕获异常:使用 GDB 的 catch throw 命令捕获所有 C++ 异常。
  2. 查看调用栈:使用 backtrace 命令查看调用栈,找到抛出异常的函数。
  3. 多线程支持:如果你的程序是多线程的,使用 info threadsthread apply all bt 来查看各线程的状态和调用栈。
  4. 调试符号:确保程序在编译时加入了调试符号(-g),这将帮助你在 GDB 中看到更多的调试信息。

通过这些步骤,你应该能够在 GDB 中轻松找到哪个函数抛出了异常,即使是在多线程环境下。

我在第二个栈帧 我能拿到什么上下文呢?

小结

当你进入某个特定的栈帧(例如第二个栈帧)时,能够获取的上下文信息包括:

  • 局部变量和函数参数:使用 info localsinfo args
  • 调用栈:使用 backtracebt 查看调用栈的上下文。
  • 源代码:使用 listframe 查看当前帧对应的源代码。
  • 寄存器状态:使用 info registers 查看寄存器的值。
  • 内存内容:使用 x 命令查看任意内存地址的内容。
  • 全局变量:使用 printinfo variables 查看全局变量的值。
  • 线程信息:使用 info threads 查看当前线程的状态。
  • 返回地址:使用 info frame 查看当前帧的返回地址。
  • 动态类型:使用 printptype 查看 C++ 对象的动态类型。

如何检索一个so里 是否定义了某种符号呢?

要检索一个共享库(.so 文件)中是否定义了某个符号(函数、变量等),你可以使用以下几种工具:

1. nm 命令

nm 是一个常用的工具,用来列出对象文件中的符号。你可以使用它来查看 .so 文件中定义的符号。

使用方法:

nm -D <your_library.so> | grep <symbol_name>
  • -D:表示显示动态符号(即只显示共享库中的符号)。
  • grep <symbol_name>:用于过滤你想要查找的符号。

示例:

nm -D libexample.so | grep my_function

如果库中定义了 my_function,则会显示类似以下的输出:

0000000000001234 T my_function

其中:

  • T 表示该符号在库中定义(T 表示符号在代码段中)。
  • U 表示该符号是未定义的(即该共享库引用了该符号,但并未定义它)。
  • 其他符号类型也存在(如 B 表示 BSS 段中的符号,D 表示数据段中的符号等)。

2. readelf 命令

readelf 是另一个工具,可以显示 ELF 文件的详细信息(ELF 是 Linux 和其他类 Unix 系统中常用的可执行文件格式)。

使用方法:

readelf -Ws <your_library.so> | grep <symbol_name>
  • -W:表示不折行输出,显示完整的符号信息。
  • -s:表示显示符号表。

示例:

readelf -Ws libexample.so | grep my_function

同样,如果库中定义了 my_function,你会看到类似下面的输出:

   Num:    Value          Size Type    Bind   Vis      Ndx Name33: 0000000000012345   100 FUNC    GLOBAL DEFAULT   12 my_function
  • FUNC 表示这是一个函数。
  • GLOBAL 表示这是一个全局符号。
  • DEFAULT 表示默认可见性。

3. objdump 命令

objdump 是另一个用于分析二进制文件的工具,也可以列出共享库中的符号。

使用方法:

objdump -T <your_library.so> | grep <symbol_name>
  • -T:表示显示动态符号表。

示例:

objdump -T libexample.so | grep my_function

输出类似于:

0000000000012345 g    DF .text  00000000000000a4  Base        my_function
  • g 表示全局符号。
  • DF 表示这是一个函数符号。
  • .text 表示函数位于代码段。

4. lddldconfig

如果你不确定是否包含在某个共享库中,lddldconfig 可以帮助你查看共享库的依赖关系,确认共享库实际被加载。

例如,使用 ldd 可以列出可执行文件所依赖的共享库:

ldd <your_executable>

这不会直接列出符号,但可以帮助你确认共享库是否被正确加载。

5. objdump + grep 自动化查找符号(可选)

你可以通过简单地结合 objdumpgrep 来自动化查找符号:

for lib in $(ldconfig -p | awk '{print $4}'); doecho "Checking $lib"objdump -T $lib | grep my_function
done

这将遍历系统中的所有共享库,检查它们是否包含 my_function 符号。

总结

要查找一个 .so 文件中是否定义了某个符号,你可以使用以下工具:

  • nm -D <your_library.so> | grep <symbol_name>:列出动态符号。
  • readelf -Ws <your_library.so> | grep <symbol_name>:查看 ELF 文件的符号表。
  • objdump -T <your_library.so> | grep <symbol_name>:显示动态符号表。

这些工具可以帮助你快速检索某个共享库是否定义了特定的符号。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ch被动扫描学习

在渗透测试中,被动扫描就像斥候一样担任前驱搜查的任务,帮助后续的渗透攻击等的深入推进提供十分重要的信息。当然,正如一双锐利的眼和好的侦查工具是一名优秀侦察兵的标配,在进行渗透测试的时候,优秀的搜索引擎就是信息收集者的”夜行衣”和“望远镜”。借助它们我们可以…

Nuxt.js 应用中的 app:suspense:resolve 钩子详解

title: Nuxt.js 应用中的 app:suspense:resolve 钩子详解 date: 2024/10/6 updated: 2024/10/6 author: cmdragon excerpt: app:suspense:resolve 是一个强大的钩子,允许开发者在异步数据解析完成后的最后一步执行必要的处理。通过合理使用该钩子,我们可以优化组件的渲染…

多校A层冲刺NOIP2024模拟赛02 csp-s模拟9

多校A层冲刺NOIP2024模拟赛02 四道题因为暑假被拉去当模拟赛 暑假集训CSP提高模拟22 了,遂直接把赛后代码交了上去,然后就被通知换题了。 原 \(100+100+100+20\) 被在 accoders NOI 上被卡成了 \(100+100+90+10\) ,更改 long long 和 int 后达到了 \(100+100+100+30\) 。 \(…

败者树、置换选择排序、最佳归并树

败者树败者树用一个数组即可实现,而且,上图中的那些方块所代表的结点是不存储在败者树中的置换选择排序 置换选择排序的目的是构造出比工作区更长的初始归并段,而更长就意味着初始归并段会更少,可能会减少归并的趟数,进而减少读写磁盘次数来优化排序时间。 置换选择排序的…

Codeforces Rund 977 div2 个人题解(A~E1)

Codeforces Rund 977 div2 个人题解(A,B,C1,C2,E1) Dashboard - Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round) - Codeforces 火车头 #define _CRT_SECURE_NO_WARNINGS 1​#include <algorithm>#include <array>#include <bitset>#inc…

ide启动多个实例

ide启动多个实例 方法一: ide 2022.X及之后 Run=> Edit Configurations=> 选中项目=> “Build and run”栏=> Modify Options=> 选中“Allow multiple instances”然后就可以run多次项目了 但是要主要改端口 方法二: 先把项目打包,然后启动多个terminal,每个…

周鸿祎:用这10条打造你的完美的商业计划书(附详细讲解)

转载:周鸿祎:用这10条打造你的完美的商业计划书(附详细讲解)_产品 (sohu.com) 江湖上流传着一篇“360大佬周鸿祎版10页商业计划书PPT”,高屋建瓴的讲述了BP制作框架,很有价值。诚然,一个形式上外观精美,具有上有吸引力的BP让人赏心悦目,但更重要的还是有实实在在的内容…

DiLiGenT光度立体数据集

本文对DiLiGenT光度立体数据集进行了详细介绍。简介 ”DiLiGenT“ 光度立体数据集,全称为 calibrated Directional Lightings, objects of General reflectance, and ‘ground Truth’ shapes (normals),即使用标定过的定向光源,对一些具有常见反射率特性的物体进行光度立体…

Pool Kings All In One

Pool Kings All In One 泳池之王 Pool Kings - Mountain Paradise / 泳池之王 - 山间天堂 Utah waterfall MountainPool Kings All In One泳池之王demosPool Kings - Mountain Paradise / 泳池之王 - 山间天堂Utah waterfall Mountainhttps://vimeo.com/233842674 https://www.…

CHT

水电费是否收到fwe】今天探索一下CTH的电脑 PEPPA PIG放映室!tm的图怎么死了

visdom可视化工具

安装visdom可视化工具 pip install visdom -i 作者:太一吾鱼水 宣言:在此记录自己学习过程中的心得体会,同时积累经验,不断提高自己! 声明:博客写的比较乱,主要是自己看的。如果能对别人有帮助当然更好,不喜勿喷! 文章未经说明均属原创,学习笔记可…

测绘地理信息赋能新质生产力

在信息化与智能化浪潮的推动下,测绘地理信息作为连接现实世界与数字空间的桥梁,正逐步成为驱动经济社会发展的新质生产力。本文旨在深入探讨测绘地理信息如何通过技术创新与应用拓展,为各行各业赋能,塑造智慧社会的新面貌。一、测绘地理信息的转型之路随着卫星定位系统(如…