推荐一个 PDF 处理工具

很多时候,我们需要对 PDF 文件进行一些简单的处理,诸如:拆分合并加水印等等。通常情况下,在线的 PDF 工具已经足够应付。但是如果考虑到隐私保密和速度的问题,本地工具会更合适一些。

最近我有一个将扫描后的 PDF 合并的需求,最后使用了PDFtk 这个工具,网站是 https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/

有兴趣的朋友可以试试。

BCDEDIT 创建一个 WinDBG 选项

本文介绍通过 BCDEDIT 创建一个 Windows 启动选项,这个选项中打开 WinDBG 功能。

根据当前已有启动项拷贝出来一个名为 “WinDBG”的启动项, 这个命令运行之后会生成一个 GUID,需要记下来

bcdedit /copy {current} /d "windbg"

设置启动超时 45秒

bcdedit /timeout 45

设置打开 Debug 功能

bcdedit /debug {前面的 GUID} on

设置使用 USB Debug

bcedit /dbgsettings usb targetname:labz

指定 XHCI

bcdedit /set "{dbgssettings}" busparams 0.20.0

这样,每次启动的时候都会出现一个菜单,选择第一项会正常启动Windows,选择第二项即可启动能够通过 WinDBG 调试的 Windows

TIPS:一种C语言奇怪的数组引用方式

最近看到了一种奇怪的写法,例如下面的代码中:Print(L”%d “,i[segM]); 这个语句,实际上是Print(L”%d “,segM[i]) 的意思。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

UINT8 segM[]={
	0xAA,0xBB,0xCC,0xDD,0xEE,0xFF
};

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
	;
	for (UINT8 i=0;i<6;i++) {
		Print(L"%d ",i[segM]);
	}
  return(0);
}

解释说数组访问操作符 [] 是‌对称的‌,即:

a[b] == b[a]

当然了,我个人是非常不建议你在代码中使用上面的方法的。

好用的C语言分析工具 CTags

CTags 是一款开源的 C语言分析工具,项目地址是:

https://github.com/universal-ctags/ctags

使用这个工具可以分析C语言,例如:

ctags C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c

可以得到下面的结果;

!_TAG_EXTRA_DESCRIPTION	anonymous	/Include tags for non-named objects like lambda/
!_TAG_EXTRA_DESCRIPTION	fileScope	/Include tags of file scope/
!_TAG_EXTRA_DESCRIPTION	pseudo	/Include pseudo tags/
!_TAG_EXTRA_DESCRIPTION	subparser	/Include tags generated by subparsers/
!_TAG_FIELD_DESCRIPTION	epoch	/the last modified time of the input file (only for F\/file kind tag)/
!_TAG_FIELD_DESCRIPTION	file	/File-restricted scoping/
!_TAG_FIELD_DESCRIPTION	input	/input file/
!_TAG_FIELD_DESCRIPTION	name	/tag name/
!_TAG_FIELD_DESCRIPTION	pattern	/pattern/
!_TAG_FIELD_DESCRIPTION	typeref	/Type and name of a variable or typedef/
!_TAG_FILE_FORMAT	2	/extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED	1	/0=unsorted, 1=sorted, 2=foldcase/
!_TAG_KIND_DESCRIPTION!C	d,macro	/macro definitions/
!_TAG_KIND_DESCRIPTION!C	e,enumerator	/enumerators (values inside an enumeration)/
!_TAG_KIND_DESCRIPTION!C	f,function	/function definitions/
!_TAG_KIND_DESCRIPTION!C	g,enum	/enumeration names/
!_TAG_KIND_DESCRIPTION!C	h,header	/included header files/
!_TAG_KIND_DESCRIPTION!C	m,member	/struct, and union members/
!_TAG_KIND_DESCRIPTION!C	s,struct	/structure names/
!_TAG_KIND_DESCRIPTION!C	t,typedef	/typedefs/
!_TAG_KIND_DESCRIPTION!C	u,union	/union names/
!_TAG_KIND_DESCRIPTION!C	v,variable	/variable definitions/
!_TAG_OUTPUT_EXCMD	mixed	/number, pattern, mixed, or combineV2/
!_TAG_OUTPUT_FILESEP	slash	/slash or backslash/
!_TAG_OUTPUT_MODE	u-ctags	/u-ctags or e-ctags/
!_TAG_OUTPUT_VERSION	1.1	/current.age/
!_TAG_PARSER_VERSION!C	2.2	/current.age/
!_TAG_PATTERN_LENGTH_LIMIT	96	/0 for no limit/
!_TAG_PROC_CWD	C:/Users/yanbwang/Downloads/ctags/	//
!_TAG_PROGRAM_AUTHOR	Universal Ctags Team	//
!_TAG_PROGRAM_NAME	Universal Ctags	/Derived from Exuberant Ctags/
!_TAG_PROGRAM_URL	https://ctags.io/	/official site/
!_TAG_PROGRAM_VERSION	6.2.0	/p6.2.20260125.0/
!_TAG_ROLE_DESCRIPTION!C!function	foreigncall	/called in foreign languages/
!_TAG_ROLE_DESCRIPTION!C!function	foreigndecl	/declared in foreign languages/
!_TAG_ROLE_DESCRIPTION!C!header	local	/local header/
!_TAG_ROLE_DESCRIPTION!C!header	system	/system header/
!_TAG_ROLE_DESCRIPTION!C!macro	undef	/undefined/
!_TAG_ROLE_DESCRIPTION!C!struct	foreigndecl	/declared in foreign languages/
AddressWidthInitialization	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^AddressWidthInitialization ($/;"	f	typeref:typename:VOID
GetFirstNonAddress	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetFirstNonAddress ($/;"	f	typeref:typename:STATIC UINT64
GetPeiMemoryCap	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetPeiMemoryCap ($/;"	f	typeref:typename:STATIC UINT32
GetSystemMemorySizeAbove4gb	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetSystemMemorySizeAbove4gb ($/;"	f	typeref:typename:STATIC UINT64
GetSystemMemorySizeBelow4gb	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetSystemMemorySizeBelow4gb ($/;"	f	typeref:typename:UINT32
InitializeRamRegions	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^InitializeRamRegions ($/;"	f	typeref:typename:VOID
PublishPeiMemory	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^PublishPeiMemory ($/;"	f	typeref:typename:EFI_STATUS
Q35TsegMbytesInitialization	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^Q35TsegMbytesInitialization ($/;"	f	typeref:typename:VOID
QemuInitializeRam	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^QemuInitializeRam ($/;"	f	typeref:typename:STATIC VOID
mPhysMemAddressWidth	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^UINT8  mPhysMemAddressWidth;$/;"	v	typeref:typename:UINT8
mQ35SmramAtDefaultSmbase	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^BOOLEAN  mQ35SmramAtDefaultSmbase = FALSE;$/;"	v	typeref:typename:BOOLEAN
mQ35TsegMbytes	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^STATIC UINT16  mQ35TsegMbytes;$/;"	v	typeref:typename:STATIC UINT16
mS3AcpiReservedMemoryBase	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^STATIC UINT32  mS3AcpiReservedMemoryBase;$/;"	v	typeref:typename:STATIC UINT32
mS3AcpiReservedMemorySize	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^STATIC UINT32  mS3AcpiReservedMemorySize;$/;"	v	typeref:typename:STATIC UINT32

类似的,可以直接分析取得C语言中的函数名,命令是:

ctags --c-kinds=f C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c

输出结果:

AddressWidthInitialization	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^AddressWidthInitialization ($/;"	f	typeref:typename:VOID
GetFirstNonAddress	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetFirstNonAddress ($/;"	f	typeref:typename:STATIC UINT64
GetPeiMemoryCap	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetPeiMemoryCap ($/;"	f	typeref:typename:STATIC UINT32
GetSystemMemorySizeAbove4gb	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetSystemMemorySizeAbove4gb ($/;"	f	typeref:typename:STATIC UINT64
GetSystemMemorySizeBelow4gb	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^GetSystemMemorySizeBelow4gb ($/;"	f	typeref:typename:UINT32
InitializeRamRegions	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^InitializeRamRegions ($/;"	f	typeref:typename:VOID
PublishPeiMemory	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^PublishPeiMemory ($/;"	f	typeref:typename:EFI_STATUS
Q35TsegMbytesInitialization	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^Q35TsegMbytesInitialization ($/;"	f	typeref:typename:VOID
QemuInitializeRam	C:/BuildBs/edk2202302/edk2/OvmfPkg/Bhyve/PlatformPei/MemDetect.c	/^QemuInitializeRam ($/;"	f	typeref:typename:STATIC VOID

Step to UEFI (302)VC 中的机器码填充

VC 的 X86 模式下无法直接使用 _ASM 关键字定义汇编代码,最近偶然看到了VC 支持Intrinsics(内联函数),它是编译器提供的特殊函数,它们直接映射到特定的处理器指令,让你能够在 C++ 代码中使用底层硬件功能。

于是,编写代码测试:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>


INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
    UINT32 cpuInfo[4];
    
    // CPUID 指令
    __cpuid(cpuInfo, 1);
	
	//NOP 指令
	__nop();
  return(0);
}

使用 /FACS 查看生成的汇编代码,对应片段如下:

ShellAppMain PROC					; COMDAT

; 19   : {

$LN3:
  00000	48 89 54 24 10	 mov	 QWORD PTR [rsp+16], rdx
  00005	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  0000a	53		 push	 rbx
  0000b	48 83 ec 10	 sub	 rsp, 16

; 20   :     UINT32 cpuInfo[4];
; 21   :     
; 22   :     // CPUID 指令
; 23   :     __cpuid(cpuInfo, 1);

  0000f	b8 01 00 00 00	 mov	 eax, 1
  00014	33 c9		 xor	 ecx, ecx
  00016	0f a2		 cpuid
  00018	4c 8d 04 24	 lea	 r8, QWORD PTR cpuInfo$[rsp]
  0001c	41 89 00	 mov	 DWORD PTR [r8], eax
  0001f	41 89 58 04	 mov	 DWORD PTR [r8+4], ebx
  00023	41 89 48 08	 mov	 DWORD PTR [r8+8], ecx
  00027	41 89 50 0c	 mov	 DWORD PTR [r8+12], edx

; 24   : 	
; 25   : 	__nop();

  0002b	90		 npad	 1

; 26   : 	 
; 27   :   return(0);

  0002c	33 c0		 xor	 eax, eax

; 28   : }

  0002e	48 83 c4 10	 add	 rsp, 16
  00032	5b		 pop	 rbx
  00033	c3		 ret	 0
ShellAppMain ENDP

可以看到编译后生成了对应的汇编代码(也就是机器码)。特别是 _NOP() 函数,直接生成了0x90 这样的机器码。使用这样的思路可以在代码中使用 _NOP()函数进行填充,然后在编译完成后使用工具替换这部分机器码为我们自定义的代码。让我们的程序更加灵活,实现更多的功能。

记录一个ESP32 S3/P4 上奇怪的问题

最近我在使用 ESP32 P4 时遇到了一个奇怪的问题,经过简化,代码如下:

class Ch346 {
  public:
    Ch346() {};
    uint8_t WriteData() {
      for (int i = 0; i < 512; i++) {
        Serial.println(i);
      }
    }
};

Ch346 MyCh346;

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("Start");
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    if (c == '1') {
      Serial.println("Input 1");
      MyCh346.WriteData();
      Serial.println("Input 1 Complete");
    }
  }

}

运行之后,打开串口输入1 然后应该得到 0-511的值,上述代码在Leonardo 板子上上没问题,但是不知道为什么在  ESP32 S3 P4上会一直输出:

===========================================================

有兴趣的朋友可以先思考一下,具体解析在下面:

CH397 UEFI 网络功能测试

CH397是 WCH 推出的USB转LAN芯片,WCH提供了对应的 UEFI驱动,用户有机会在 UEFI 环境中直接使用这个网卡。本次试验是在UEFI Shell 下启动一个 HTTP服务器,然后通过网络内另外一台机器的浏览器对这台机器进行访问。

这次我们使用一台机器作为 DHCP 服务器,能够给被测机器提供IP地址,具体的配置请看之前的文章。

第一步,Boot到UEFI 下,然后 load WCHUSBNIC.EFI 加载驱动

正确加载后,使用 ifconfig -l 列出当前系统中的网卡

第二步,运行如下命令让网卡通过 DHCP分配地址(如果你接入的网络没有DHCP,那么就需要手工指定网卡使用 IP)

Ifconfig -s eth0 dhcp

这步之后,再次运行 ifconfig -l 可以看到网卡的 MAC 还有分配到的 IP地址

第三步,我们在UEFI Shell 下运行一个 webserver

第四步,在主机端浏览器上使用 上面分配到的IP地址即可访问被测机。

具体的工作测试视频,可以在这里看到:

WCHUSBNIC.EFI 下载

TPS63802 升降压芯片模块测试

TPS63802DLAR 是 TI 推出降压/升压转换器。

• 输入电压范围:1.3V 至 5.5V – 器件启动时输入电压大于 1.8V

• 输出电压范围:1.8V 至 5.2V(可调节)

• VI ≥ 2.3V、VO = 3.3V 时,输出电流为 2A

这里使用这个芯片制作一个模块,对于外部输入的 1.8-5.5V 的电压转化为 3.3V。

焊接之后如下

接下来对模块进行测试:

1.7V输入时,模块不工作

1.8V时开始工作,输出3.3V

2.4V时输出3.3V

3.4V时输出3.3V

4.4V时输出3.3V

5.4V时输出3.3V

可见,这个芯片非常适合配合 3.7V 充电电池使用。

模块的电路图和PCB下载(立创标准版)

C# 程序:统计文本文件中未曾出现的 ASCII

一段简单的代码,找到给定的文本中哪个 ASCII 字符没有出现过

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace FilterAscii
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Usage: AsciiCharCounter <filename>");
                return;
            }

            string filename = args[0];
            if (!File.Exists(filename))
            {
                Console.WriteLine($"File not found: {filename}");
                return;
            }

            bool[] charPresence = new bool[128];
            foreach (char c in File.ReadAllText(filename))
            {
                if (c < 128) charPresence[c] = true;
            }

            Console.WriteLine("Missing ASCII characters:");
            for (int i = 0; i < 128; i++)
            {
                if (!charPresence[i])
                {
                    Console.WriteLine($"'{(char)i}' (ASCII {i})");
                }
            }
        }
    }
}

我用这个工具分析一个Debug BIOS 串口输出结果如下,没有出现过的大部分 ASCII 都是不可见的,只有 $ 和 ^ 这两个可见的没有输出过

Arduino中ESP32 P4 中一次性设置多个 GPIO的方法

一般情况下我们可以用过 digitalWrite() 在 Arduino 中设置 GPIO 的 HIGH/LOW, 但是这种方法非常耗时。这里给出一种可以一次性设置多个 GPIO 的方法:

在 C:\Users\USERNAME\AppData\Local\Arduino15\packages\esp32\tools\esp32-arduino-libs\idf-release_v5.4-858a988d-v1-cn\esp32p4\include\soc\esp32p4\register\soc\gpio_struct.h 有如下定义:

extern gpio_dev_t GPIO;

typedef struct gpio_dev_t {
    volatile gpio_bt_select_reg_t bt_select;
    volatile gpio_out_reg_t out;
    volatile gpio_out_w1ts_reg_t out_w1ts;
    volatile gpio_out_w1tc_reg_t out_w1tc;
    volatile gpio_out1_reg_t out1;
    volatile gpio_out1_w1ts_reg_t out1_w1ts;
    volatile gpio_out1_w1tc_reg_t out1_w1tc;
    uint32_t reserved_01c;
    volatile gpio_enable_reg_t enable;
    volatile gpio_enable_w1ts_reg_t enable_w1ts;
    volatile gpio_enable_w1tc_reg_t enable_w1tc;
    volatile gpio_enable1_reg_t enable1;
    volatile gpio_enable1_w1ts_reg_t enable1_w1ts;
    volatile gpio_enable1_w1tc_reg_t enable1_w1tc;
    volatile gpio_strap_reg_t strap;
    volatile gpio_in_reg_t in;
    volatile gpio_in1_reg_t in1;
    volatile gpio_status_reg_t status;
    volatile gpio_status_w1ts_reg_t status_w1ts;
    volatile gpio_status_w1tc_reg_t status_w1tc;
    volatile gpio_status1_reg_t status1;
    volatile gpio_status1_w1ts_reg_t status1_w1ts;
    volatile gpio_status1_w1tc_reg_t status1_w1tc;
    volatile gpio_intr_0_reg_t intr_0;
    volatile gpio_intr1_0_reg_t intr1_0;
    volatile gpio_intr_1_reg_t intr_1;
    volatile gpio_intr1_1_reg_t intr1_1;
    volatile gpio_status_next_reg_t status_next;
    volatile gpio_status_next1_reg_t status_next1;
    volatile gpio_pin_reg_t pin[57];
    volatile gpio_func_in_sel_cfg_reg_t func_in_sel_cfg[256]; /* func0-func255: reserved for func0, 46, 67, 72, 73, 79, 81, 82, 84, 85, 87, 88, 115, 116, 119-125, 157, 204-213 */
    volatile gpio_func_out_sel_cfg_reg_t func_out_sel_cfg[57];
    volatile gpio_intr_2_reg_t intr_2;
    volatile gpio_intr1_2_reg_t intr1_2;
    volatile gpio_intr_3_reg_t intr_3;
    volatile gpio_intr1_3_reg_t intr1_3;
    volatile gpio_clock_gate_reg_t clock_gate;
    uint32_t reserved_650[44];
    volatile gpio_int_raw_reg_t int_raw;
    volatile gpio_int_st_reg_t int_st;
    volatile gpio_int_ena_reg_t int_ena;
    volatile gpio_int_clr_reg_t int_clr;
    volatile gpio_zero_det_filter_cnt_reg_t zero_det_filter_cnt[2];
    volatile gpio_send_seq_reg_t send_seq;
    volatile gpio_recive_seq_reg_t recive_seq;
    volatile gpio_bistin_sel_reg_t bistin_sel;
    volatile gpio_bist_ctrl_reg_t bist_ctrl;
    uint32_t reserved_728[53];
    volatile gpio_date_reg_t date;
} gpio_dev_t;

其中的out_w1ts 和 out_w1tc 可以用来一次性设置多个 GPIO 为高或者为低。

测试代码:

#include "soc/gpio_struct.h" // GPIO

void setup() {
   pinMode(7,OUTPUT);pinMode(8,OUTPUT);
   pinMode(30,OUTPUT);pinMode(32,OUTPUT);pinMode(33,OUTPUT);pinMode(49,OUTPUT);
   pinMode(50,OUTPUT);pinMode(52,OUTPUT);
   digitalWrite(7,LOW);digitalWrite(8,LOW);
   digitalWrite(30,LOW);digitalWrite(32,LOW);digitalWrite(33,LOW);digitalWrite(49,LOW);
   digitalWrite(50,LOW);digitalWrite(52,LOW);

}

void loop() {
    GPIO.out_w1ts.val = (1<<30)|(1<<8)|(1<<7); 
    GPIO.out1_w1ts.val=(1<<(33-32))|(1<<(32-32))|(1<<(52-32))|(1<<(49-32))|(1<<(50-32));
    GPIO.out_w1tc.val = (1<<30)|(1<<8)|(1<<7); 
    GPIO.out1_w1tc.val=(1<<(33-32))|(1<<(32-32))|(1<<(52-32))|(1<<(49-32))|(1<<(50-32));
    delay(1000);
}

硬件上GPIO33, 32, 30, 8, 7, 52, 49, 50分别连接到逻辑分析的4-11通道。

其中的 GPIO 初始化使用 Arduino 代码来完成,GPIO 的设置直接使用位操作。超过31的GPIO要在out1_w1ts和out1_w1tc上设置。

最终抓到的结果可以从下面看到:

参考:

1.https://github.com/maarten-pennings/howto/blob/main/esp32-fast-gpio/esp32-fast-gpio.md