最近实验了一下 WinDBG 调试 VirtualBox 中的 Win10,简单介绍一下方法:
1. VirtualBox的设置(特别注意,名称上最好不要用 com1 之类的,有可能会和系统设备冲突)
2.WinDbg的设置
3.进入 Windows10 之后打开 debug 功能。在 Windows看起来是通过串口进行调试的
4. 启动 WinDBG 进行调试
Arduino 模拟鼠标目前有三种方法:
1.外部加电阻 USB母头等元件,然后烧录模拟程序,用 328P作为处理器。这种方法的缺点是:原件多,不容易调试,占用板载资源多,做出来之后基本上不能完成什么功能了(因为Usb低速设备传输要求1.5Mb/s,而328P最高只有16MHz),有兴趣的朋友可以看一下之前我做的一个锁屏的装置【参考1】;
2.直接使用Leonardo 这样主控是ATmega32u4【参考2】 的板子。这种方法的好处是:Arduino 原生库支持,资料比较多,调试方便。个人推荐初学者如果有鼠标键盘的需要可以玩这个;
3.原版的Arduino Uno 上面使用的串口芯片是 16u2,可以给这个芯片刷写上一个特殊的Firmware,它和PC端用USB鼠标或者键盘通讯,然后和 328P 使用串口通讯。
本文介绍的就是第三种方法。
在玩第三种方法的时候,你需要特别准备一个烧写器。我用的是 USBTINY 这款。
本次实验的目标是将uno模拟成鼠标。参考的资料来自下面的页面:
http://hunt.net.nz/users/darran/weblog/cca39/Arduino_UNO_Mouse_HID.html
我刷写的工具是 AvrDudess 2.4,用法很简单,接线之后(建议选购下载器的时候直接选带完整线的,否则每次接线也是很麻烦的事情),按下 Detect按钮,软件需要检查到正确芯片的类型,比如,我的转接芯片是 16u2。如果无法侦测,那么请检查连线。如果折腾了很久都不行,那么请联系卖家所要驱动和刷写工具。刚开始的时候我就在这里折腾了很长时间。
因为串口芯片被刷掉了,所以接下来也必须使用刷写器写入编译好的Arduino 程序。

输入程序,确定编译无误
/* Arduino USB Mouse HID demo */
/* Author: Darran Hunt
* Release into the public domain.
*/
struct {
uint8_t buttons;
int8_t x;
int8_t y;
int8_t wheel; /* Not yet implemented */
} mouseReport;
uint8_t nullReport[4] = { 0, 0, 0, 0 };
void setup();
void loop();
void setup()
{
Serial.begin(9600);
delay(200);
}
/* Move the mouse in a clockwise square every 5 seconds */
void loop()
{
int ind;
delay(5000);
mouseReport.buttons = 0;
mouseReport.x = 0;
mouseReport.y = 0;
mouseReport.wheel = 0;
mouseReport.x = -2;
for (ind=0; ind<20; ind++) {
Serial.write((uint8_t *)&mouseReport, 4);
Serial.write((uint8_t *)&nullReport, 4);
}
mouseReport.x = 0;
mouseReport.y = -2;
for (ind=0; ind<20; ind++) {
Serial.write((uint8_t *)&mouseReport, 4);
Serial.write((uint8_t *)&nullReport, 4);
}
mouseReport.x = 2;
mouseReport.y = 0;
for (ind=0; ind<20; ind++) {
Serial.write((uint8_t *)&mouseReport, 4);
Serial.write((uint8_t *)&nullReport, 4);
}
mouseReport.x = 0;
mouseReport.y = 2;
for (ind=0; ind<20; ind++) {
Serial.write((uint8_t *)&mouseReport, 4);
Serial.write((uint8_t *)&nullReport, 4);
}
}
接线如下:
用 IDE 上传内容,需要一些设置,指定刷写工具

然后使用 File->Upload Using Programmer 来进行上传

上传成功:

成功之后,用Arduino Usb口连接电脑,你的鼠标每隔一段会自动旋转一圈,同时在设备管理器中会出现一个鼠标设备:

这个和16u2 Firmware source code(Descriptors.c)中定义是相同的
.VendorID = 0x03EB,
.ProductID = 0x2041,
.ReleaseNumber = 0x0000
最后,特别提醒:设计模拟USB鼠标键盘之类的程序时,一定要考虑留多加一个运行条件。比如:某个引脚设定为低时才运行,或者上电10s才运行。否则有可能出现程序正常,但是因为键盘鼠标的干扰你没有再重新刷写新的代码的机会。
本文提到的16u2特别的 Firmware 下载 Arduino-mouse-0.1 源程序 arduino-mouse-0.1.tar
参考:
1. http://www.lab-z.com/20140101/ 用 Arduino 打造一个自动锁屏装置
2. http://www.arduino.cn/thread-1205-1-1.html Arduino Leonardo 中文介绍
上一次【参考1】介绍的屏幕旋转项目中还带有 Shell Command的内容。就是说可以把他调用驱动的Application”包”到Shell 中。这次介绍一下具体的实现。
源代码放置的位置目录和上一次实验相同。之后进行下面的步骤:
1.在 C:\EDK\ShellPkg\ShellPkg.dsc的
ShellPkg/Application/Shell/Shell.inf {
<LibraryClasses>
NULL|ShellPkg/Library/UefiShellLevel2CommandsLib/UefiShellLevel2CommandsLib.inf
NULL|ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellLevel3CommandsLib/UefiShellLevel3CommandsLib.inf
!ifndef $(NO_SHELL_PROFILES)
NULL|ShellPkg/Library/UefiShellDriver1CommandsLib/UefiShellDriver1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellInstall1CommandsLib/UefiShellInstall1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellDebug1CommandsLib/UefiShellDebug1CommandsLib.inf
NULL|ShellPkg/Library/UefiShellNetwork1CommandsLib/UefiShellNetwork1CommandsLib.inf
!ifdef $(INCLUDE_DP)
NULL|ShellPkg/Library/UefiDpLib/UefiDpLib.inf
!endif #$(INCLUDE_DP)
!endif #$(NO_SHELL_PROFILES)
##LABZDEBUG_Start
NULL|GopRotatePkg/Library/GopRotateShellCommandLib/GopRotateShellCommandLib.inf
##LABZDEBUG_End
}
2. 使用编译命令 重新编译 Shell。具体方法还可以在【参考2】看到
build by build -a IA32 -p ShellPkg\ShellPkg.dsc -b RELEASE
3.正常编译之后shell.efi 可以在这个目录中找到 C:\EDK\Build\Shell\RELEASE_MYTOOLS\IA32
4.从C:\EDK\Nt32Pkg\Nt32Pkg.fdf) 可以看到,NT32Pkg 用的是 FullShell
5.用生成的Shell .efi 替换C:\EDK\EdkShellBinPkg\FullShell\Ia32中的 Shell_Full.efi
6.用 Build 重新编译Nt32 项目,然后再用 Build run 运行模拟器
7.在模拟器中先加载Driver Load GopRotate.efi
8.枚举一下当前Shell中有 GraphicsOutput Protocol支持的 Device Handle。模拟器中有两个设备,分别对应2个窗口

下面是重新编译通过的 shell 有兴趣的朋友可以直接使用
参考:
1. http://www.lab-z.com/stu88/ Step to UEFI (88) 一个转屏驱动
2. http://www.lab-z.com/how2buildshell/ Step to UEFI (35) —– How to build Shell.efi
初学 Watcom C的时候遇到一个问题“如何访问指定的内存”。当时为了这个问题花费了不少功夫,最后才发现直接用指针就可以进行访问,因为过于简单以至于网上都没有人问过…….
最近看 UEFI 编程,同样也遇到了这个问题,我去查找了 mm命令的source code,看到了它使用了PCI Root Bridge I/O Protocol 这个Protocol,然后就去研究之。同样越研究越迷糊,最终发现虽然这个Protocol提供了内存访问函数,但是本质上依然使用指针来直接访问。出于保护模式下的内存,任何其他方法都有“脱了裤子放屁—–多此一举”之嫌。
参考 mm 源程序,很快写出代码:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/DeviceIo.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
typedef enum {
EfiPciWidthUint8,
EfiPciWidthUint16,
EfiPciWidthUint32,
EfiPciWidthUint64
} DUMMY;
VOID
ReadMem (
EFI_IO_WIDTH Width,
UINT64 Address,
UINTN Size,
VOID *Buffer
)
{
do {
if (Width == EfiPciWidthUint8) {
*(UINT8 *) Buffer = *(UINT8 *) (UINTN) Address;
Address -= 1;
} else if (Width == EfiPciWidthUint16) {
*(UINT16 *) Buffer = *(UINT16 *) (UINTN) Address;
Address -= 2;
} else if (Width == EfiPciWidthUint32) {
*(UINT32 *) Buffer = *(UINT32 *) (UINTN) Address;
Address -= 4;
} else if (Width == EfiPciWidthUint64) {
*(UINT64 *) Buffer = *(UINT64 *) (UINTN) Address;
Address -= 8;
} else {
Print(L"Can't read memory at %X",Width);
break;
}
//
//
//
Size--;
} while (Size > 0);
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
CHAR8 *Mem1;
UINT32 Buffer;
Mem1=AllocatePool(4);
*(Mem1+0)='L';
*(Mem1+1)='A';
*(Mem1+2)='B';
*(Mem1+3)='Z';
Print(L"Memory Address: %X\n",Mem1);
Print(L"%X\n",*Mem1);
Print(L"%X\n",*(Mem1+1));
Print(L"%X\n",*(Mem1+2));
Print(L"%X\n",*(Mem1+3));
ReadMem(EfiPciWidthUint32, (UINT64)Mem1, 1, &Buffer);
Print(L"Read[%X]=%X\n",Mem1,Buffer);
FreePool(Mem1);
return EFI_SUCCESS;
}
在NT32模拟器中实验时发现,模拟环境中不是所有的内存空间都是可以直接访问的。稍有不慎就会得到错误信息,模拟器也会随之崩溃。于是,代码是创建一个4 Bytes长的内存空间,写入一些字符,然后再 ReadMem 读取出来,这样做能够保证访问的内存是可以被正常操作的。
运行结果:
参考的 mm.c 来自EfiShell 1.06\Shell\mm\mm.c。
mm
本文提到的完整代码下载
ReadMEM
UDK2015 出来一段时间了,之前的文章也介绍过【参考1】。只是有一个严重的问题: UDK2015 下面 C编写的工具是高版本的 Visual Studio 编译的,并且没有设置对于 XP 的兼容,于是 XP 下面无法直接使用这些工具。一种解决的办法是重新编译用来build的工具,另外一种就是更换你的操作系统为 Windows 7。
周末花了一点时间在虚拟机中安装了一个 Windows7 然后配置了 Vs2008和 UDK2015 ,于是目前可以做到虚拟机下面的 UDK2015 的编译了。有需要的朋友可以直接下载,安装 Virtual Box 之后就可以直接使用。
链接:http://pan.baidu.com/s/1jIfdzhk 密码:x04z
2016/06/10 补充
今天尝试在上面安装 EADK 发现直接编译不过,后来比较了一下文件发现 UDK2015 ShellPkg中的一些Pkg被删掉了,AppPkg.dsc 中有用到。我尝试从 UDk2014 中直接比较补充了那几个 Pkg,编译能够通过。不过这样的话,建议有需要的朋友同时安装 UDK2014。
参考:
1.http://www.lab-z.com/udk2015/ UDK2015来了
这次介绍一个通过驱动程序旋转屏幕的项目,地址是https://github.com/apop2/GopRotate 。项目的简介是“A EDK2 Package that supplies a UEFI driver that will bind on top of Graphics Output Devices and rotate any BLT operations by 0, 90, 180 or 270 degrees.”。
本文并不打算做原理上的分析,只是介绍如何编译和实验。
实验环境是 UDK2014
1.在 C:\EDK\Nt32Pkg\Nt32Pkg.dsc 文件的 [Components] 段中添加下面的内容
MdeModulePkg/Application/VariableInfo/VariableInfo.inf MdeModulePkg/Universal/PlatformDriOverrideDxe/PlatformDriOverrideDxe.inf ##LABZDebug_Start GopRotatePkg/GopRotate/GopRotate.inf ##LABZDebug_End ################################################################################################### # # BuildOptions Section - Define the module specific tool chain flags that should be used as # the default flags for a module. These flags are appended to any
2.将 GopRotatePkg 目录拷贝到你UDK 的根目录下 例如: C:\EDK\
3.使用 Build 命令编译 NT32
4.使用 build run 运行模拟器
至此,驱动程序已经编译完成。下面要编译使用这个驱动的 Application。
5.将GopRot 按照一个普通的Application编译
编译完成后可以进行实验了。
6.使用 load goprotate.efi 加载驱动

7.输入 goprot.efi 2 进行测试。
运行之前的屏幕是这样的:

运行之后屏幕就变成这样了

完整的代码下载
前面提到的驱动项目完整代码
GopRotatePkg
调用驱动的应用程序代码
GopRot
一个标准的 Arduino Uno上面有两个可以编程的IC,一个是负责USB 转串口的ATmega16U2,一个主控芯片ATmega328P,下图红色标记的就是16u2,绿色标记的是 328P.

然后对应的有三种Firmware: 16U2 中有一个, 328P 中有两个。16u2的负责USB转串口;328P的一个Firmware是BootLoader,从功能上说主要是负责把 16u2收到串口数据刷新到328P 上;328P中的另外一个 Firmware 就是我们平常写的程序,编译之后生成的,用来完成我们期望的功能。
一般情况下,如果想更新16u2,需要额外的设备,比如 USB IPS ; 我们IDE只能更新328P 中的程序部分.328P 的BootLoader也是需要额外的设备来进行更新的。更新 16u2使用下图左上角框住部分的排针,更新 328P 使用下图中间橘色框图中指出的引脚。

16u2的Firmware 可以在类似 \arduino-1.6.3\hardware\arduino\avr\firmwares\atmegaxxu2\arduino-usbserial 的路径中找到
328P Bootloader 的Firmware 可以在\arduino-1.6.3\hardware\arduino\avr\bootloaders\atmega 的路径中找到。
查看UEFI下的大小写转换函数的时候,偶然发现了EFI_UNICODE_COLLATION_PROTOCOL【参考1】提供了几个有意思的函数。
具体的头文件定义在 \MdePkg\Include\Protocol\UnicodeCollation.h
///
/// The EFI_UNICODE_COLLATION_PROTOCOL is used to perform case-insensitive
/// comparisons of strings.
///
struct _EFI_UNICODE_COLLATION_PROTOCOL {
EFI_UNICODE_COLLATION_STRICOLL StriColl;
EFI_UNICODE_COLLATION_METAIMATCH MetaiMatch;
EFI_UNICODE_COLLATION_STRLWR StrLwr;
EFI_UNICODE_COLLATION_STRUPR StrUpr;
//
// for supporting fat volumes
//
EFI_UNICODE_COLLATION_FATTOSTR FatToStr;
EFI_UNICODE_COLLATION_STRTOFAT StrToFat;
///
/// A Null-terminated ASCII string array that contains one or more language codes.
/// When this field is used for UnicodeCollation2, it is specified in RFC 4646 format.
/// When it is used for UnicodeCollation, it is specified in ISO 639-2 format.
///
CHAR8 *SupportedLanguages;
};
根据介绍,大概的介绍一些功能(如果你发现有错误,欢迎eMail指出)
EFI_UNICODE_COLLATION_STRICOLL StriColl; //大小写不敏感的比较函数
EFI_UNICODE_COLLATION_METAIMATCH MetaiMatch; //正则表达式匹配
EFI_UNICODE_COLLATION_STRLWR StrLwr; //字符串转小写
EFI_UNICODE_COLLATION_STRUPR StrUpr; //字符串转大写
EFI_UNICODE_COLLATION_FATTOSTR FatToStr; //8.3格式的OEM定义字符文件名转String
EFI_UNICODE_COLLATION_STRTOFAT StrToFat; //String转8.3格式的OEM定义字符
CHAR8 *SupportedLanguages; //列出当前系统支持的语言代码
之后,根据上面的介绍,编写一个测试例子:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/UnicodeCollation.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status;
EFI_UNICODE_COLLATION_PROTOCOL *mUnicodeCollation;
CHAR16 *TestStr=L"wWw.LaB-z.cOm";
CHAR16 *Pattern1=L"w*";
CHAR16 *Pattern2=L"*z.c*";
CHAR16 *Pattern3=L"c*";
Status = gBS->LocateProtocol(
&gEfiUnicodeCollation2ProtocolGuid,
NULL,
&mUnicodeCollation);
if (EFI_ERROR (Status)) {
Print(L"Can't Locate Protocol\n");
return Status;
}
mUnicodeCollation->StrLwr(mUnicodeCollation,TestStr);
Print(L"%s\n",TestStr);
mUnicodeCollation->StrUpr(mUnicodeCollation,TestStr);
Print(L"%s\n",TestStr);
Print(L"%d\n",(mUnicodeCollation->
MetaiMatch(mUnicodeCollation,
TestStr,
Pattern1)));
Print(L"%d\n",(mUnicodeCollation->
MetaiMatch(mUnicodeCollation,
TestStr,
Pattern2)));
Print(L"%d\n",(mUnicodeCollation->
MetaiMatch(mUnicodeCollation,
TestStr,
Pattern3)));
return EFI_SUCCESS;
}
其中测试了大小写转换不必细说,多说两句关于正则表达式的用法:
CHAR16 *TestStr=L"wWw.LaB-z.cOm"; CHAR16 *Pattern1=L"w*"; CHAR16 *Pattern2=L"*z.c*"; CHAR16 *Pattern3=L"c*";
其中 “*” 表示匹配一个或者任意多个字符, Pattern1 表示的是“以w开头的字符串”;Pattern2 表示的是“中间含有 z.c 字符的字符串”;Pattern3 表示的是“以c开头的字符串”。最终运行结果如下:
完整的代码下载:
UnicTest
参考:
1. UEFI 2.4 P592
Base64编码出现的背景【参考1】:电子邮件的传输需要把原始内容编码为可见的ASCII来进行传输,很早之前出现的电子邮件编码规则兼容性不太好,比如没有考虑邮件的多种内容的问题,还有对文件音频视频附件之类兼容不好。因此,提出来新的编码,这种新的编码格式编解码很简单,同时编码后的内容只比编码之前大33%,这就是Base64。
这里是来自网上【参考2】的一份 Arduino base64库,下面简单介绍一下用法:
int base64_encode(char *output, char *input, int inputLen); 对字符串进行base64编码
int base64_decode(char *output, char *input, int inputLen); 对Base64字符串进行b解码
int base64_enc_len(int inputLen); “预测”Base64编码后的字符串长度
int base64_dec_len(char *input, int inputLen); “预测”Base64编码字符串解码后的字符串长度
下面是一个完整的例子【参考2】:
#include <Base64.h>
/*
Base64 Encode/Decode example
Encodes the text "Hello world" to "SGVsbG8gd29ybGQA" and decodes "Zm9vYmFy" to "foobar"
Created 29 April 2015
by Nathan Friedly - http://nfriedly.com/
This example code is in the public domain.
*/
void setup()
{
// start serial port at 9600 bps:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.println("Base64 example");
// encoding
char input[] = "Hello world";
int inputLen = sizeof(input);
int encodedLen = base64_enc_len(inputLen);
char encoded[encodedLen];
Serial.print(input); Serial.print(" = ");
// note input is consumed in this step: it will be empty afterwards
base64_encode(encoded, input, inputLen);
Serial.println(encoded);
// decoding
char input2[] = "Zm9vYmFy";
int input2Len = sizeof(input2);
int decodedLen = base64_dec_len(input2, input2Len);
char decoded[decodedLen];
base64_decode(decoded, input2, input2Len);
Serial.print(input2); Serial.print(" = "); Serial.println(decoded);
}
void loop()
{
}
运行结果:
这里【参考4】,提供了一个在线版的Base64编解码工具,可以用来检查结果是否正确。
完整的代码下载:
sketch_apr20a
最后,之前我还介绍过MD5的 Arduino 库【参考3】,有兴趣的朋友也可以研究一下。
参考:
1. http://www.faqs.org/rfcs/rfc2045.html
2. https://github.com/adamvr/arduino-base64 库下载
arduino-base64-master
3. http://www.lab-z.com/arduinomd5/ Arduino 的MD5库
4. http://www1.tc711.com/tool/BASE64.htm 在线编码解码