当前位置:网站首页>OpenOCD-JTAG调试
OpenOCD-JTAG调试
2020-11-09 16:56:00 【whoisliang】
目录
title: OpenOCD-JTAG调试
tags: ARM
date: 2018-10-13 23:36:28
---
Todo
- [ ] JTAG 调试linux内核
- [ ] linux下使用OpenOCD调试
- [x] win下使用OpenOCD调试
概述
-
学习文档 韦东山 Eclipse,OpenOCD,OpenJTAGv3.1嵌入式开发教程版本5.pdf
-
硬件连接: PC>JTAG调试器>CPU
-
软件控制:IDE(KEIL/ADS/)> GDB(指令)> OpenOCD(实际命令)> JTAG调试器> 单板
- JTAG控制CPU功能:
- 当CPU的地址信号ADDR=xxx,停止CPU-硬件断点
- 当CPU的数据信号DATA=xxx,停止CPU--软件断点
- 重新运行CPU
- 读取R0,..寄存器
- 控制外设,内存
-
百问网的
OpenJTAG.exe这个GUI实际是封装了openocd.exe命令行- 设置Workdir到程序代码目录
- 点击
telnet,或者直接cmd输入telnet 127.0.0.1 4444 - 使用help查看帮助或者查看`Eclipse,OpenOCD,OpenJTAGv3.1嵌入式开发教程版本5.pdf
断点
-
硬件断点:一个程序只能打两个断点(ARM7),可以调试ROM,NOR
设置CPU里面的JTAG比较器,使硬件断点 Addr=A,当CPU发出A地址时停止
CPU是如何运行?CPU需要取指令,也就是需要发出地址信号去取得指令,JTAG就检测到这个地址
-
软件断点,可以有无数个软件断点.前提是断点的地址可写,所以无法调试NOR,或者ROM上的程序,具体可以看下面的led.c的示例
- 设置CPU里面的JTAG比较强,使其值=一个特殊值
- 替换掉希望断点位置(A)的值=这个特殊值,做好备份
- 当CPU读取到这个特殊值,程序断点
- 重新运行时,恢复这个(A)位置的指令
快速使用
常用命令
-
halt 停止cpu -
reg 查看寄存器 -
mdw 0 //memory display word 查看内存 -
mww 0 0x12345678 //memory write word -
load_image leds.bin 0 //下载程序到0地址,然后可以使用mdw读取0,看看是不是程序的bin -
resume 0 //指定地址运行,如果不指定地址,则恢复运行 -
reset 复位目标板子 -
reset halt -
-
step 0 //执行第一句话,并halt -
step //单步执行 -
-
bp设置断点 -
bp 0x6c 4 hw 在0x6c的地址位置设置断点,硬件断点 -
rpb 0x6c 取消断点
测试led的断点
-
//led.c -
void wait(volatile unsigned long dly) -
{ -
for(; dly > 0; dly--); -
} -
-
int main(void) -
{ -
unsigned long i = 0; -
-
GPFCON = GPF4_out|GPF5_out|GPF6_out; -
-
while(1){ -
wait(30000);-------------------尝试在这里断点,就能观察灯的变化 -
GPFDAT = (~(i<<4)); -
if(++i == 8) -
i = 0; -
} -
-
return 0; -
} -
-
-
//反汇编摘要 -
00000044 <main>: -
44: e52de004 str lr, [sp, #-4]! -
48: e24dd004 sub sp, sp, #4 ; 0x4 -
4c: e3a03000 mov r3, #0 ; 0x0 -
50: e58d3000 str r3, [sp] -
54: e3a03456 mov r3, #1442840576 ; 0x56000000 -
58: e2833050 add r3, r3, #80 ; 0x50 -
5c: e3a02c15 mov r2, #5376 ; 0x1500 -
60: e5832000 str r2, [r3] -
64: e3a00c75 mov r0, #29952 ; 0x7500 -
68: e2800030 add r0, r0, #48 ; 0x30 -
6c: ebffffe9 bl 18 <wait>---------------------------尝试在这里断点 -
70: e3a02456 mov r2, #1442840576 ; 0x56000000 -
74: e2822054 add r2, r2, #84 ; 0x54 -
78: e59d3000 ldr r3, [sp] -
7c: e1a03203 mov r3, r3, lsl #4 -
80: e1e03003 mvn r3, r3 -
84: e5823000 str r3, [r2] -
88: e59d3000 ldr r3, [sp] -
8c: e2833001 add r3, r3, #1 ; 0x1 -
90: e58d3000 str r3, [sp] -
94: e3530008 cmp r3, #8 ; 0x8 -
98: 1afffff1 bne 64 <main+0x20> -
9c: e3a03000 mov r3, #0 ; 0x0 -
a0: e58d3000 str r3, [sp] -
a4: eaffffee b 64 <main+0x20> -
Disassembly of section .debug_line:
使用openocd命令来调试断点
-
halt -
load_image leds.bin 0 //下载程序 -
resume 0 //指定地址运行,如果不指定地址,则恢复运行 -
halt -
bp 0x6c 4 hw //在0x6c的地址位置设置断点,硬件断点 -
-
resume //反复这个断点,就能观察灯的变化了
测试软件断点是否改变ram值
-
//读取原来的值 -
> mdw 0x6c -
0x0000006c: ebffffe9 -
//设置软件断点 -
> bp 0x6c 4 -
breakpoint set at 0x0000006c -
//读回这个ram值,发现改变了 -
> mdw 0x6c -
0x0000006c: deeedeee -
//删除这个断点 -
> rbp 0x6c -
//读回来 -
> mdw 0x6c -
0x0000006c: ebffffe9
NAND调试(进阶)
程序概述:
- 链接地址在
0x30000,0000,运行的时候应该位于0x3000,0000 - 直接烧写程序到内存中,也就是程序运行在0地址.并没有在加载地址
- 程序功能:main中点灯,但是直接烧录到内部ram,跑飞了
- 程序的bug在于,所有的代码都应该是位置无关的否则需要搬运代码
-
.text -
.global _start -
_start: -
@函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义 -
ldr sp, =4096 @设置堆栈 -
bl disable_watch_dog @关WATCH DOG -
bl memsetup @初始化SDRAM -
bl nand_init @初始化NAND Flash -
-
@将NAND Flash中地址4096开始的1024字节代码(main.c编译得到)复制到SDRAM中 -
@nand_read_ll函数需要3个参数: -
ldr r0, =0x30000000 @1. 目标地址=0x30000000,这是SDRAM的起始地址 -
mov r1, #0 @2. 源地址 = 0 -
mov r2, #4096 @3. 复制长度= 2048(bytes), -
bl nand_read @调用C函数nand_read -
-
ldr sp, =0x34000000 @设置栈 -
ldr lr, =halt_loop @设置返回地址 -
ldr pc, =main @b指令和bl指令只能前后跳转32M的范围 -
@,所以这里使用向pc赋值的方法进行跳转 -
halt_loop: -
b halt_loop
bug原因:mem_cfg_val是在栈,是位置无关的,但是他的初始值是去在链接地址取的,也就是0x3000000后面
-
void memsetup() -
{ -
unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON -
0x00000700, //BANKCON0 -
0x00000700, //BANKCON1 -
0x00000700, //BANKCON2 -
0x00000700, //BANKCON3 -
0x00000700, //BANKCON4 -
0x00000700, //BANKCON5 -
0x00018005, //BANKCON6 -
0x00018005, //BANKCON7 -
0x008C07A3, //REFRESH -
0x000000B1, //BANKSIZE -
0x00000030, //MRSRB6 -
0x00000030, //MRSRB7 -
}; -
-
}
调试开始
-
>reset halt -
> load_image nand.bin 0 -
1520 bytes written at address 0x00000000 -
downloaded 1520 bytes in 0.063003s (23.560 KiB/s)
执行第一句话step 0也就是执行mov sp, #4096 ; 0x1000,可以使用reg看到sp=0x1000
step执行跳转,可以发现pc=0x38,对应汇编
-
30000000 <_start>: -
30000000: e3a0da01 mov sp, #4096 ; 0x1000 -
30000004: eb00000b bl 30000038 <disable_watch_dog>
然后一步一步step,并使用poll查看当前pc值等,使用mdw查看该设置的内存
跳转到memsetup,pc=0x08,反汇编先入栈,可以发现sp=4096-5*4=4076=0xFEC
可以使用 step 0,然后设置 bp 0x50 4 hw 断点 直接跳到想到的位置
然后在汇编代码,发现问题了
-
// ip=300005bc -
30000050: e1a0400c mov r4, ip -
30000054: e8b4000f ldmia r4!, {r0, r1, r2, r3} -
//从r4中指向的内存给r0~r3 -
-
//也就是说从 300005bc读取一些数据,但是这个时候300005bc(sdram)并没有被初始化且进行代码搬运,所以这里的数据肯定有问题 -
-
//这里的其实就是那个局部数组的值 mem_cfg_val -
300005bc <.rodata>: -
300005bc: 22011110 andcs r1, r1, #4 ; 0x4 -
300005c0: 00000700 andeq r0, r0, r0, lsl #14 -
300005c4: 00000700 andeq r0, r0, r0, lsl #14 -
300005c8: 00000700 andeq r0, r0, r0, lsl #14 -
300005cc: 00000700 andeq r0, r0, r0, lsl #14 -
300005d0: 00000700 andeq r0, r0, r0, lsl #14 -
300005d4: 00000700 andeq r0, r0, r0, lsl #14 -
300005d8: 00018005 andeq r8, r1, r5 -
300005dc: 00018005 andeq r8, r1, r5 -
300005e0: 008c07a3 addeq r0, ip, r3, lsr #15 -
300005e4: 000000b1 streqh r0, [r0], -r1 -
300005e8: 00000030 andeq r0, r0, r0, lsr r0 -
300005ec: 00000030 andeq r0, r0, r0, lsr r0 -
Disassembly of section .comment:
OpenOCD
全称是(Open On-Chip Debugger)
使用前都需要打开OpenOCD,连接上开发板,然后打开telent,或者使用命令telnet 127.0.0.1 4444
启动OpenOCD

专家模式:对应比较自由高级的配置,我们一般直接用普通模式即可
- Interface
对应OpenOCD\0.4.0\interface`中的选项 Target对应OpenOCD\0.4.0\ target和OpenOCD\0.4.0\board
启用telnet
Win7默认没有打开这个功能,需要在程序和功能>打开或关闭 windows 功能> TelentClient打开
OpenOCD命令
这些命令都在telnet中运行,官方命令索引在这里,PDF文档OpenOCD User's Guide.pdf
-
记住的命令
-
reset halt -
resume -
step -
load_image
-
-
目标板状态处理命令(Target state handling)
-
poll 查询目标板当前状态 -
halt 中断目标板的运行 -
resume [address] 恢复目标板的运行,如果指定了 address,则从 address 处开始运行 -
step [address] 单步执行,如果指定了 address,则从 address 处开始执行一条指令 -
reset 复位目标板
-
-
断点命令
-
bp <addr> <length> [hw] 在地址 addr 处设置断点,指令长度为 length, hw 表示硬件断点 -
rbp <addr> 删除地址 addr 处的断点 内存访问指令(Memory access commands)
-
-
内存访问指令(Memory access commands)
-
mdw ['phys'] <addr> [count] 显示从(物理)地址 addr 开始的 count(缺省是 1)个字(4 字节) -
mdh ['phys'] <addr> [count] 显示从(物理)地址 addr 开始的 count(缺省是 1)个半字(2 字节) -
mdb ['phys'] <addr> [count] 显示从(物理)地址 addr 开始的 count(缺省是 1)个字节 -
mww ['phys'] <addr> <value> 向(物理)地址 addr 写入一个字,值为 value -
mwh ['phys'] <addr> <value> 向(物理)地址 addr 写入一个半字,值为 value -
mwb ['phys'] <addr> <value> 向(物理)地址 addr 写入一个字节,值为 value
-
-
内存装载命令,注意:下载程序之前先使用“ halt” 命令暂停单板,才能下载代码;如果使用“ poll” 命令发现单板的 MMU 或 D-cache 已经使能,则需要使用“ arm920t cp15 2 0” 、“ step”两条命令禁止 MMU 和 D-cache。
-
load_image <file> <address> [‘bin’|‘ihex’|‘elf’] -
======将文件<file>载入地址为 address 的内存,格式有‘bin’、 ‘ihex’、 ‘elf’ -
dump_image <file> <address> <size> -
======将内存从地址 address 开始的 size 字节数据读出,保存到文件<file>中 -
verify_image <file> <address> [‘bin’|‘ihex’|‘elf’] -
======将文件<file>与内存 address 开始的数据进行比较,格式有‘bin’、 ‘ihex’、 ‘elf’
-
-
CPU 架构相关命令(Architecture Specific Commands)
-
reg 打印寄存器的值 -
arm7_9 fast_memory_access ['enable'|'disable'] -
=======使能或禁止“快速的内存访问” -
arm mcr cpnum op1 CRn op2 CRm value 修改协处理器的寄存器 -
=======比如: arm mcr 15 0 1 0 0 0 关闭 MMU -
arm mrc cpnum op1 CRn op2 CRm 读出协处理器的寄存器 -
=======比如: arm mcr 15 0 1 0 0 读出 cp15 协处理器的寄存器 1 -
arm920t cp15 regnum [value] 修改或读取 cp15 协处理器的寄存器 -
=======比如 arm920t cp15 2 0 关闭 MMU
-
-
其他命令
script <file> 执行 file 文件中的命令

OpenOCD烧录程序
-
load_image <file> <address> [‘bin’|‘ihex’|‘elf’] -
======将文件<file>载入地址为 address 的内存,格式有‘bin’、 ‘ihex’、 ‘elf’ -
====load_image led.bin 0
SDRAM初始化
OpenOCD可以快速读写SDRAM,前提是需要程序先初始化好SDRAM,这样之后可以烧录u-boot到sdram,然后通过u-boot烧录其他大程序.这里需要一段初始化sdram的位置无关的代码init.bin
-
//从 Nand Flash 启动 -
load_image init/init.bin 0x0 -
resume 0x0 -
//NOR 启动 -
load_image init/init.bin 0x40000000 -
resume 0x40000000
烧录u-boot,之后打开串口,就能看到跑起来了
-
halt -
load_image u-boot/u-boot.bin 0x33f80000 -
resume 0x33f80000
uboot烧录其他程序(led/uboot)
- u-boot 的命令把所有的数字当作 16 进制,所以不管是否在数字前加前缀“ 0x”,这个数
字都是 16 进制的。 - 擦除 Flash 的长度、烧写的数据长度,这些数值都是根据要烧写的
文件的长度确定的。 u-boot.bin 的长度是 178704 字节,即 0x2BA10 字节,使用的长
度都是 0x30000,一是为了与 Flash 的可擦除长度相配(16K 的整数倍),二是方便。


GDB
- linux下 arm-linux-gdb,win下arm-elf-gdb
-
arm-elf-gdb nand_elf -
target remote 127.0.0.1:3333 -
load
GDB命令
| 启动/退出 | |
|---|---|
| gdb [FILE] arm-elf-gdb [FILE] arm-linux-gdb [FILE] | 启动 gdb,调试 FILE(也可以先不指定文件) |
| quit | 退出 gdb |
| target remote ip:port | 远程连接 |
| 文件操作 | |
| file | 载入文件 FILE,注意:不会下载到单板上 |
| load [FILE] | 把文件下载到单板上,如果不指定 FILE,则下载之前指定 过的(比如 file 命令指定的,或是 gdb 运行时指定的文件) |
| 查看源程序 | |
| list | 列出某个函数 |
| list | 以当前源文件的某行为中间显示一段源程序 |
| list | 接着前一次继续显示 |
| break * | 在某个地址上设置断点,比如 break *0x84 |
| list - | 显示前一次之前的源程序 |
| list list | 显示指定文件的一段程序 |
| info source | 查看当前源程序 |
| info stack | 查看堆栈信息 |
| info args | 查看当前的参数 |
| 断点操作 | |
| break | 在函数入口设置断点 |
| break | 在当前源文件的某一行上设置断点 |
| break | 在指定源文件的某一行上设置断点 |
| info br | 查看断点 |
| delete | 删除断点 |
| diable | 禁止断点 |
| enable | 使能断点 |
| 监视点(watch)操作 | |
| watch | 当指定变量被写时,程序被停止 |
| rwatch | 当指定变量被读时,程序被停止 |
| 数据操作 | |
| print < EXPRESSION > | 查看数据 |
| set varible=value | 设置变量 |
| x /NFU ADDR | 检查内存值 ① N 代表重复数 ② F 代表输出格式 x : 16 进制整数格式 d : 有符号十进制整数格式 u : 无符号十进制整数格式 f : 浮点数格式 ③ U 代表输出格式: b :字节(byte) h :双字节数值 w :四字节数值 g :八字节数值 比如“ x /4ub 0x0”将会显示 0 地址开始到 4 个字节 |
| 执行程序 | |
| step next nexti | 都是单步执行: step 会跟踪进入一个函数, next 指令则不会进入函数 nexti 执行一条汇编指令 |
| continue | 继续执行程序,加载程序后也可以用来启动程序 |
| 帮助 | |
| help [command] | 列出帮助信息,或是列出某个命令的帮助信 |
| 其他命令 | |
| monitor <command …> | 调用 gdb 服务器软件的命令,比如:“ monitor mdw 0x0” 就是调用 openocd 本身的命令“ mdw 0x0” |
使用条件
-
代码已经重定位,处于它的链接地址.为什么?【在源文件设置断点,其实是在链接地址设置断点,是根据链接地址去修改内存(软断点)】
-
链接脚本必须是按照固定格式
text,data,bss分开 -
被调试的程序中含有debug信息,也就是编译elf时有
-g选项-
%.o:%.c -
arm-linux-gcc -Wall -c -g -O2 -o $@ $< -
-
%.o:%.S -
arm-linux-gcc -Wall -c -g -O2 -o $@ $<
-
-
FAQ: 无法调试重定位的代码,那么代码搬运与sdram初始化怎么办? 使用opencod来执行,也就是再弄一个程序初始化sdram,使用openocd烧录
使用步骤
-
打开openocd,打开telent
-
如果是需要使用sdram的程序,则先在OpenOCD中先下载初始化sdram的程序
-
> load_image init/init.bin 0 -
> resume 0 -
> halt -
target state: halted -
target halted in ARM state due to debug-request, current mode: Supervisor -
cpsr: 0x200000d3 pc: 0x000000b8 -
MMU: disabled, D-Cache: disabled, I-Cache: enabled -
-
// 测试一下 sdram可用 -
> mdw 0x30000000 -
0x30000000: ea000017 -
> mww 0x30000000 0x12345678 -
> mdw 0x30000000 -
0x30000000: 12345678
-
-
打开cmd,输入
arn-elf-gdb leds_elf启动gdb,指定程序 -
连接到OpenOCD
target remote 127.0.0.1:3333 -
下载程序
load
直接使用命令脚本 gdb.init : arm-elf-gdb -x gdb.init leds_elf
-
target remote localhost:3333 -
monitor halt -
monitor arm920t cp15 2 0 -
monitor step -
load -
break main -
continue
注意 gdb运行之后没有断点之后停不了了,需要在telnet使用halt,然后GDB界面才能继续输入
常用命令
-
si 执行一条指令 -
braek main.c:21 //在main.c的21行打断点 -
c 或者 continue 继续运行 -
-
print i //查看变量值

Eclipes
Eclipse 是 gdb(包括 arm-elf-gdb, arm-linux-gdb)的图形化前台,使用 Eclipse 进行调试实质
上是使用 gdb 进行调试。
使用 gdb 进行调试时, gdb 会用到程序的链接地址。比如在 main 函数打断点, gdb 会根
据 main 函数的链接地址找到内存上对应的指令,修改这条指令为一条特殊的指令。当程序执
行到这条特殊的指令时,就会停止下来。[也就是软件断点]
使用条件
- 程序应该位于它的链接地址上
- 如果用到SDRAM,先初始化SDRAM,然后下载程序到链接地址
简单工程
-
点击图标
Workbench
-
新建一个C工程
File -> New -> C Project,选择“ Makefile project->Empty Projects”、“ Other Toolchain” -
导入文件在
File -> Import中的General>File System -
工程设置
-
在“ Project” 菜单里,点击去掉“ Build Automatically”前面的标
记,表示不自动编译工程 -
在“ Project” 菜单里,点击
clean,去除Start a build immediately
-
-
编译,其实在目录下直接
make也是可以的了,已经安装后工具链了- 使用
Project中的build all和build project都可以了 - 使用
clean也行了 - 注意,make是不一样的,一个是arm-linux,另一个是arm-elf
- 使用
-
调试配置
-
参考下面uboot的图配,非uboot不需要配置source选项,命令行也不需要第一个路径配置
-
去除debug中的
stop on startup at main -
project> debug config选择Zylin Native,new或者双击都可以,出现配置 -
Main> C/C++ Application选择调试的elfleds.elf -
Debugger> Debugger选择EmbeddedGDB,下面的main选择arm-elf-gdb或者是C:\Program Files\yagarto\bin\arm-elf-gdb.exe -
GDB command file 可以选择,也可以不选,其实就是提前运行的命令-
target remote localhost:3333 -
monitor halt -
monitor arm mcr 15 0 1 0 0 0 -
monitor step 0 -
load -
break main -
continue
但是虽然这里设置了断点,貌似有点问题,依然需要在
command输入-
load -
break main -
continue
-
-
注意
如果有时候没有看到debug窗口,右上角点一下debug视图,然后F5试试
有时候使用clean,需要看下是不是有debug存在着,需要关掉


u-boot工程
调试网上下载的 u-boot 时,需要定义 CONFIG_SKIP_LOWLEVEL_INIT,它表示
“跳过底层的初始始化”,就是不要初始化存储控制器,不要再次复制 u-boot 本身到 SDRAM
中。对于韦东山的的 u-boot,已经增加的自动识别代码,无需定义这个宏。
-
import相关文件
-
设置命令如下,这个是为了建立路径对应
-
set substitute-path /work/eclipse_projects/u-boot/u-boot-1.1.6_OpenJTAG E:/Eeclipse_projects/u-boot/u-boot-1.1.6_OpenJTAG -
load -
break start_armboot -
c
然后再source中删除原来的default,添加如下(这个实际是上面的命令在生效)
-
\work\projects\OpenPDA\u-boot-1.1.6_OpenJTAG -
E:\Eeclipse_projects\u-boot\u-boot-1.1.6_OpenJTAG\

-
STM32烧写程序
-
halt -
flash probe 0 -
flash write_image erase STM3210B.bin 0x08000000 -
verify_image STM3210B.bin 0x08000000

转载于:https://www.cnblogs.com/zongzi10010/p/9784797.html
版权声明
本文为[whoisliang]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/2963604/blog/4710174
边栏推荐
- 用微信表情翻译表白,程序员的小浪漫,赶紧Get起来!
- [share] interface tests how to transfer files in post request
- Configure static IP address in ubuntu18.04 NAT mode -2020.11.09
- 同事笔记-小程序入坑点
- Easyexcel exports according to the filter column (not empty in the middle, the order can be adjusted)
- Using GaN based oversampling technique to improve the accuracy of model for mortality prediction of unbalanced covid-19
- 腾讯云AMD云服务器怎么样好不好?
- Cad2016 download autocad2016 download installation detailed tutorial CAD Download
- Function calculation advanced IP query tool development
- 零基础小白python入门——深入Python中的文件操作
猜你喜欢

Easyexcel exports according to the filter column (not empty in the middle, the order can be adjusted)

echart 设置柱子之间的间距

Kubernetes v1.19.3 kubeadm deployment notes (2)

我在传统行业做数字化转型(1)预告篇

Revealing the logic of moving path selection in Summoner Canyon?

会展云技术解读 | 面对突发事故,APP 如何做好崩溃分析与性能监控?

堆重启_uaf_hacknote

Mit6.824 distributed system course translation & learning notes (3) GFS

Set two ways of background image, and solve the mobile phone background image highly adaptive problem

MES system is different from traditional management in industry application
随机推荐
为什么现在开发一款软件的时间越来越长?
Explore cache configuration of Android gradle plug-in
CAD2016软件安装教程
深入分析商淘多用户商城系统如何从搜索着手打造盈利点
Gesture switch background, let live with goods more immersive
Kubernetes-17:Kubernets包管理工具—&gt;Helm介绍与使用
5 minutes get I use GitHub's five-year summary of these complaints!
解析:C++如何实现简单的学生管理系统(源码分享)
脑机接口先驱炮轰马斯克:“他走的是一条死胡同,说的话我一个字都不同意”
Low power Bluetooth single chip helps Internet of things
The worst hacker in history: stealing $1 billion of bitcoin without spending it for seven years, and finally being seized by the Department of justice
史上最惨黑客:偷走10亿美元比特币7年未花,最终被司法部全数缴获
Toolkit Pro助力界面开发:缩短项目开发周期,快速实现具有现代功能区样式的GUI
树莓派内网穿透建站与维护,使用内网穿透无需服务器
我在传统行业做数字化转型(1)预告篇
ABBYY FineReader 15新增编辑页面布局功能
MES系统在行业应用里区别于传统式管理
手势切换背景,让直播带货更加身临其境
微服务框架 Go-Micro 集成 Nacos 实战之服务注册与发现
融云集成之避坑指南-Android推送篇