总结和展望:质量比较大点,就不容易被风吹动

又到了一年一度的总结和展望。时间过得真快,2012年定计划的那会好想就发生在昨天。还能清晰的记得当时的计划,当然也是因为计划定的足够的简单。当然还是把个人的计划放在后面,先来总结下2013年的工作。这年的工作真是富有戏剧性,工作中做了一些网页前端的工作,这确实让我措手不及,不过既然组里有这样的需求,也只能硬着头皮上了,还好是这也并不算忙,不影响个人计划的进展。另一方面,公司拿我们部门和其他公司合并了,
换句话说,我们部门被裁了,只不过以一种漂亮的方式。就如同所有的接纳新员工的老大一样,新公司的leader会给你谈未来画大饼。只不过,对不起,我真的不看好这种抱团取暖的合并,所以,我选择离开。由于平时有一定的积累,所以找份靠谱的工作也并不是特别难的事情。能预感到新的工作会比较忙,不过我想,应该还是能hold住的。

当然,要说最放不下的,要数公司的健身房。掐指一算,已经坚持锻炼了16个月了!依稀记得12年是拼命跑25分钟能跑到4km多点,而13年已经能跑过5km了。一年的时间,让我在25分钟里能超越过去的自己两圈,“想想还有点小激动呢!”。另外肱二头肌和肱三头肌已经比较明显了,上臂粗了好多,穿短袖看起来MAN了好多,不过腹肌虽然能看出来,但还是不算特别明显。这里不得不提醒一下sysdbg的博主,“我督促你健身了16个月,你是不是应该请我吃金钱豹啊?!别客气,跪谢就免了,嗯!”。噢!说到这货,我还想到了一个事情,就是练字。现在字终于写的有点人样了,虽然写急了还是很丑,但是应该比以前好点了吧……大概……是这样。

我记得,13年的个人计划只有一个就是山寨sysinternals的工具。这次算是完成的比较好吧,工具集中大部分小工具都山寨了,只有个别界面特别复杂,功能特别强大的工具没有山寨。实在是没有动力写界面。另外,又一次把minikernel重写了一次,也围绕这个发布了不少的blog,但是这个minikernel还是缺少挺多的东西,比如很核心的多进程和多线程。一方面是突然遇到公司方面的事情打断了进度,另一个方面也是自己懒。

至于2014年的个人计划,还真不好说,还不知道新工作是个什么情况,但是我个人还是比较想写一个脚本语言以及完善这个minikernel的。另外继续健身,练字也是必须的。最近因为灌篮高手高清重置版播出了,也导致我又想没事抽出点时间打打球了。另外在13年,花了很多时间看dota2的视频,这个也是被sysdbg的博主吐槽了好久,最近也已经开始戒dota了,时间真是越来越不够用。
希望15年写总结的时候,能看到一个质量更大的自己吧,希望写的这些计划能完成75%以上。

最后,还是祝福家人,朋友,自己在新的一年里幸福安康,合家欢乐!!!

XPerfHelper —— XPerf的Windows命令行脚本生成工具

使用过XPerf的应该都知道,写一个XPerf的命令行是多么的麻烦,如果不太熟悉,需要反复的查看帮助里的参数。所以一般情况下,大家会把命令写到一个cmd或者bat的脚本中,这样就可以双击来使用XPerf,只需要第一次费点心思写脚本罢了。但是我还是觉得,即使是只用写一次脚本,也还是挺麻烦的,于是写了这个小工具,生成cmd脚本文件。妈妈再也不用担心我的XPerf命令写错了。

20131201221223

如上图所示,我们可以选择kernel flag和stackwalk,然后选择providers,点击OK,生成cmd文件即可。下面是一个生成的cmd的内容:

20131201222315

下载XPerfHelper

Debugging

使用ETW对程序进行监控和分析

ETW(Event Tracing for Windows)是Windows提供的对程序进行事件记录,跟踪,使用的机制。我们可以利用这个机制对程序进行调试和性能分析。从Windows Vista开始,ETW已经非常好的融合在Windows内核之中了,在Windows 7开始,这一个机制更加完善,几乎记录了Windows运行的每一个细节。我个人猜测,从Windows Vista开始到Windows 7直到现在的Windows 8,性能都在不断提高,ETW机制应该是功不可没的。

ETW是Windows提供的机制,我们要使用他还需要工具,这些工具我们可以自己开发,因为Windows提供了调用接口。当然,更惬意的选择就是直接使用Windows提供的工具集WPT(Windows Performance Toolkit)。利用WPT和SDK,我们可以将ETW融入到我们自己的程序中,帮助我们调试程序和提升性能。

以下是我们需要的工具:
SDK:

  1. ECMangen.exe
  2. mc.exe

Windows:
WEVTUtil.exe

WPT:

  1. XPerf.exe
  2. XPerfView.exe

首先我们需要用SDK中的工具ECMangen,生成一个manifest文件,这个文件用来描述记录的事件,如图。
20131117114336
这里,我们首先创建一个Provider,接下来创建一个Event,参数可以随便填下,就像上图所示。作为演示,这里一个Event就够了(FirstEvent)。
然后保存为0CChProvider.man。

接下来我们MC来生成一个头文件,一个资源文件和两个二进制文件。命令行如:
mc -um C:\etw\0CChProvider.man
20131117114801

然后我们可以创建一个工程,加入这个头文件和资源文件。并且在代码中插入写事件的代码,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "stdafx.h"
#include
#include "../../0CChProvider.h"


int _tmain(int argc, _TCHAR* argv[])
{
EventRegisterMy0CChProvider();
EventWriteFirstEvent(L"Hello ETW World!");
Sleep(1000);
EventWriteFirstEvent(L"Bye ETW World!");
EventUnregisterMy0CChProvider();
return 0;
}

在这里,我们利用EventRegisterMy0CChProvider先注册自己的Provider,接下写事件才会发挥作用。记录事件后,我们需要反注册Provider。
好了,演示代码就这么一点,然后编译即可。

接下来我们需要注册这个Provider给系统,需要使用到系统自带的工具WEVTUtil.exe。
wevtutil im C:\etw\0CChProvider.man

注册成功后就可以利用XPerf开启ETW,然后运行程序,查看记录的事件了。

  1. xperf -start 0cch -on My0CChProvider:::’stack’
  2. xperf -on base
  3. Run 0CChProvider.exe
  4. xperf -stop 0cch -stop -d d:\0cch.etl
  5. xperf d:\0cch.etl

XPerfView会生成分析数据如图:
20131117115853

20131117120007

整体来说,想简单的使用ETW也就是这么简单,当然你也可以把他弄得很复杂,这里就不介绍了。话说sysdbg早就让我写点XPerf的东西,但是因为各种懒没写,刚好最近终于有空了,就先写了这么个简单的介绍,就当作一个开篇吧,话说某人的Blog好久没更新了呀。。。

DebuggingNTInternals

Windbg script中获得调试环境的基本信息

今天继续来玩Windbg script。在写复杂的脚本的时候,可能需要根据调试的环境,指定不同的脚本代码来运行。而Windbg貌似没有提供很好的方式,让脚本得知调试环境。还好,我们可以用一些其他的方式获得这些信息,例如:写一个扩展程序来设置这些信息到Aliase上,0cchext就实现了这个功能。另外一个方式就是使用脚本自身来获得一些简单的信息,算是个windbg script中的小把戏吧。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$$ Initialize script environment
$$ Author: nighxie
$$ Blog: 0cch.net
$$ @#NtMajorVersion @#NtMinorVersion - System version number.
$$ @#DebugMode - 0:kd 1:lkd 2:user

ad /q ${/v:$sharedata}

.catch {
.foreach /pS 2 (${/v:$addr} {!kuser}) {
aS ${/v:$sharedata} ${$addr};
.leave;
}
}

.block {
r @$t0=${$sharedata};
aS /x ${/v:@#NtMajorVersion} @@C++(((nt!_KUSER_SHARED_DATA *)@$t0)->NtMajorVersion);
aS /x ${/v:@#NtMinorVersion} @@C++(((nt!_KUSER_SHARED_DATA *)@$t0)->NtMinorVersion);
}

ad /q ${/v:$sharedata}

.catch {
r @$t0 = 0;
.foreach (${/v:$addr} {lm1m m nt}) {
r @$t0 = ${$addr};
.leave;
}
}

.if ($vvalid(@$t0, 1)) {
aS ${/v:@#DebugMode} 0;
.foreach (${/v:$val} {.catch{? @eax}}) {
.if ($scmp("${$val}", "\'@eax\'")==0) {
aS ${/v:@#DebugMode} 1;
}
}
}
.else {
aS ${/v:@#DebugMode} 2;
}

DebuggingTips

Windbg script的notepad++语法高亮配置文件

经常写复杂的windbg脚本的程序员肯定知道,windbg脚本的宏替换的执行方式,让人非常的不舒服。另外windbg的脚本也没有一个好用的语法高亮编辑器,所以让脚本写起来更加痛苦。前者看来是已成定局,很难解决了。不过后者还是有机会改善的,闲暇之余,写了一个notepad++上的windbg脚本的语法高亮配置文件。以上一篇文章中的windbg脚本为例,高亮效果如下图:

20131015164715

导入方式也非常简单,点击[语言]菜单下的define your language,在弹出的对话框中点击导入按钮,导入配置文件即可。

20131015164704

下载脚本wds

Debugging

Windbg内核调试查看窗口句柄信息的脚本

十一长假瞬间就结束了,整一周都在玩,也没有研究什么好玩的东西,这里就分享一个以前写的windbg脚本吧。通途是内核调试查看窗口句柄信息。用法很简单,例如 $$>a<hwnd.wds 000207B8。运行结果如下图:

20131007195716

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
$$ Convert HWND to tagWnd
$$ Author: nighxie
$$ Blog: 0cch.net

.if (${/d:$arg1}) {

.if (${/d:$arg2}) {
.if (${$arg2} == 1) {
r $t0 = nt!PsActiveProcessHead
.for (r $t1 = poi(@$t0);(@$t1 != 0) & (@$t1 != @$t0);r $t1 = poi(@$t1)) {
r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks);
as /x ${/v:$ProcAddr} @$t2;
as /ma ${/v:$ImageName} @@c++(&@$t2->ImageFileName[0]);

.block {
$$ .echo ${$ImageName}
.if ($sicmp("${$ImageName}", "explorer.exe") == 0) {
.echo Found the process at ${$ProcAddr};
.process /p /r ${$ProcAddr};
ad ${/v:$ImageName};
ad ${/v:$ProcAddr};
.break;
}
}

ad ${/v:$ImageName};
ad ${/v:$ProcAddr};
}
}
}


r @$t1 = ${$arg1};
r @$t0 = win32k!gSharedInfo;
.if ((@$t1&0xffff) < @@C++(((win32k!tagSHAREDINFO *)@$t0)->psi->cHandleEntries)) {
r @$t0 = @@C++(((win32k!tagSHAREDINFO *)@$t0)->aheList);
r @$t0 = @@C++(@$t0+(@$t1&0xffff)*sizeof(win32k!_HANDLEENTRY));
r @$t0 = poi(@$t0);
.printf "HWND: %p\n", @@C++(((win32k!tagWnd *)@$t0)->head.h);
.printf /D "tagWnd * @ %p\n", @$t0;
.if (@@C++(((win32k!tagWnd *)@$t0)->strName.Buffer) != 0) {
.printf "Window Name: %mu\n", @@C++(((win32k!tagWnd *)@$t0)->strName.Buffer);
}
.printf /D "tagCLS * @ pcls) win32k!tagCLS\">%p\n", @@C++(((win32k!tagWnd *)@$t0)->pcls);
.if (@@C++(((win32k!tagWnd *)@$t0)->pcls->lpszAnsiClassName) != 0) {
.printf "Window Class Name: %ma\n", @@C++(((win32k!tagWnd *)@$t0)->pcls->lpszAnsiClassName);
}
.if (@@C++(((win32k!tagWnd *)@$t0)->spwndNext) != 0) {
.printf "Next Wnd: %p\n", @@C++(((win32k!tagWnd *)@$t0)->spwndNext->head.h);
}
.if (@@C++(((win32k!tagWnd *)@$t0)->spwndPrev) != 0) {
.printf "Previous Wnd: %p\n", @@C++(((win32k!tagWnd *)@$t0)->spwndPrev->head.h);
}
.if (@@C++(((win32k!tagWnd *)@$t0)->spwndParent) != 0) {
.printf "Parent Wnd: %p\n", @@C++(((win32k!tagWnd *)@$t0)->spwndParent->head.h);
}
.if (@@C++(((win32k!tagWnd *)@$t0)->spwndChild) != 0) {
.printf "Child Wnd: %p\n", @@C++(((win32k!tagWnd *)@$t0)->spwndChild->head.h);
}
.if (@@C++(((win32k!tagWnd *)@$t0)->spwndOwner) != 0) {
.printf "Own Wnd: %p\n", @@C++(((win32k!tagWnd *)@$t0)->spwndOwner->head.h);
}
.if (@@C++(((win32k!tagWnd *)@$t0)->lpfnWndProc) != 0) {
.printf /D "pfnWndProc: head.pti->pEThread)->Tcb.Process);u @@C++(((win32k!tagWnd *)@$t0)->lpfnWndProc)\">%p\n", @@C++(((win32k!tagWnd *)@$t0)->lpfnWndProc);
}
.printf "Visiable: %d\n", @@C++((((win32k!tagWnd *)@$t0)->style & (1<<28)) != 0);
.printf "Child: %d\n", @@C++((((win32k!tagWnd *)@$t0)->style & (1<<30)) != 0);
.printf "Minimized:%d\n", @@C++((((win32k!tagWnd *)@$t0)->style & (1<<29)) != 0);
.printf "Disabled: %d\n", @@C++((((win32k!tagWnd *)@$t0)->style & (1<<27)) != 0);
.printf "Window Rect { %d, %d, %d, %d}\n", @@C++(((win32k!tagWnd *)@$t0)->rcWindow.left), @@C++(((win32k!tagWnd *)@$t0)->rcWindow.top), @@C++(((win32k!tagWnd *)@$t0)->rcWindow.right), @@C++(((win32k!tagWnd *)@$t0)->rcWindow.bottom);
.printf "Clent Rect { %d, %d, %d, %d}\n", @@C++(((win32k!tagWnd *)@$t0)->rcClient.left), @@C++(((win32k!tagWnd *)@$t0)->rcClient.top), @@C++(((win32k!tagWnd *)@$t0)->rcClient.right), @@C++(((win32k!tagWnd *)@$t0)->rcClient.bottom);

}
.else {
.printf "HWND is out of range.\n";
}

}
.else {
.echo "Usage $$>a<${$arg0} HWND(HEX)"
.echo "e.g. $$>a<${$arg0} 0x60962"
}


DebuggingTips

解析youku视频地址的python脚本

几个月前写的东西,偶然翻出来发现还能用,就贴出来吧。非专业python程序员,代码比较乱 :-(

20130928134507

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
'''
Created on 2013-3-6

@author: nightxie
'''

import sys
import urllib.request
import urllib.parse
import json

def GetKeyString(seed):
base_string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890';
target_string = '';
while len(base_string) != 0 :
seed = (seed * 211 + 30031) % 65536;
index = (seed / 65536 * len(base_string));
target_string += base_string[int(index)];
base_string = base_string[:int(index)] + base_string[int(index)+1:];
return target_string;

def GetFildId(daye_str, seed):

new_list = daye_str.split('*');
target_string = '';
base_string = GetKeyString(seed);
length = len(new_list);
i = 0;
while i < length - 1:
index = int(new_list[i]);
target_string += base_string[index];
i += 1;
return target_string;

def GetFlvPath(videoids, flv_type):
url = 'http://v.youku.com/player/getPlayList/VideoIDS/' + videoids + '/timezone/+08/version/5/source/video'
req = urllib.request.Request(url);
req.add_header('Referer', 'http://static.youku.com/v1.0.0307/v/swf/player_yk.swf');
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17');
play_list = urllib.request.urlopen(req).read().decode("utf8");

play_list_data = json.loads(play_list)['data'][0];
seed = play_list_data['seed'];
streamfileids = play_list_data['streamfileids'];
regs = play_list_data['segs'];

type_fileid = streamfileids[flv_type];
type_list = regs[flv_type];
url_type = flv_type;
if url_type == 'hd2':
url_type = 'flv'

type_list_count = len(type_list);
i = 0;
while i < type_list_count :
fileid = GetFildId(type_fileid, seed);
fileid = fileid[:8] + ("%0.2X" % i) + fileid[10:];
fileid += '?k=';
fileid += type_list[i]['k'];
flv_path = 'http://f.youku.com/player/getFlvPath/sid/00_00/st/'+url_type+'/fileid/' + fileid;
print(flv_path);
i += 1;

def GetVideoIdsFromUrl(url):
url_object = urllib.parse.urlparse(url);
url_path = url_object.path;
return url_path[11:-5]

def GetFlvPathFromUrl(url, video_type):
video_ids = GetVideoIdsFromUrl(url);
GetFlvPath(video_ids, video_type);

if __name__ == '__main__':
if len(sys.argv) == 3:
GetFlvPathFromUrl(sys.argv[1], sys.argv[2]);
else:
print("youku.py ");


Tips

Floppy Disk Controller编程

N82077AA

看的没错,这篇文章将描述一些关于Floppy Disk Controller的编程知识。我们知道,在当今的计算机硬件体系中,软盘驱动器是一个已经完全被淘汰的设备,那么为什么还要有这样一篇文章?原因很简单,如果想构建自己的操作系统,必须有相应的存储介质,而软盘正是这样一个好的存储介质。他的容量虽然很小,但是却完全足够应付我们的内核程序,另一方面软盘驱动器的控制相对于之前我所介绍的硬盘的控制要简单的多,而且关于软盘驱动器控制的教程和文章在互联网上也非常的多(虽然绝大部分都是英文的)。基于以上几点,我认为还是有必要把自己学到的知识写下了分享。

Floppy Disk Controller,中文称为:软盘控制器,简称:FDC,是一个用来控制软盘驱动器的芯片。在1980年代到1990年代,软盘控制器普遍使用于个人电脑以及与IBM PC兼容的机型上,如 8272A、82078、82077SL以及82077AA,其中82077AA是最先进的一款芯片(1991年开始生产)。除了软盘控制器,软驱本身也在几十年的历史中留下了许多机型,如图所示:
floppy_types
实际上,我从刚刚接触软盘到最后软盘被淘汰,只使用过3.5英寸1.44MB的软盘,其他型号完全没有接触过。

对于CPU还运行在实模式下的启动引导程序和内核程序,我们可以调用BIOS提供的函数来完成软盘的访问,其中中断号是13h(INT13h),功能号为2(AH=2)是读取操作,功能号为3(AH=3)时是写入操作。实模式下通过中断读写软盘的资料很多(包括中文资料),如果想了解更多的实模式下访问软盘的知识,可以上网google一下,我这里就不做详细的介绍了。

虽然调用中断访问软盘简单,但是我们不能让自己的内核总是跑在实模式下啊。所以我们需要写一个能跑在保护模式下的软驱驱动,要完成这样的驱动,就必须对FDC进行编程了。不过在此之前,我们需要知道,到底PC上有没有软驱。要获得这个信息,我们需要读取CMOS,然后解析读取的信息即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mov dx, 70h
mov al, 10h
out dx, al

mov dx, 71h
in al, dx

mov f_b, al
and f_b, 0fh
shr al, 4
mov f_a, al

f_a db 0h
f_b db 0h

要从CMOS中获得软盘信息,我们需要先给对应的端口设置正确的索引,然后再去数据端口读取数据。具体做法是设置0x70端口为0x10,然后读取0x71端口。读取到的信息都放在一个字节中,需要把字节分为两个部分,高四位是驱动器A的类型索引号,低四位是驱动器B的类型索引号。索引号与软盘类型的对应关系如下图所示:
cmos_floppytype

在确认了软驱存在的情况下,接下来就可以对FDC进行编程了。先来看看FDC的几个基本知识。

寻址方式
软盘驱动器使用CHS寻址方式。软盘介质总是有两个头(面),但磁道数和每个磁道的扇区扇区数是不一定的。通常情况下,1.44mb的软盘,他有80个磁道和每个磁道有18个扇区。另外值得注意的是,磁道和磁头是从0开始计算,但是扇区是从1开始计数的。即,有效的磁道通常为0到79,磁头为0或1,而扇区号是1到18的。如果访问0号扇区,那么一定会引起访问错误的。

数据传输方式
和硬盘的数据传输方式一样,软盘也支持PIO和DMA两种数据传输方式。
软盘使用的通常是ISA DMA方式(这和 PCI BusMastering DMA完全是两码事)。使用DMA传输的方法,简单来说就是这是DMA的通道2,如传输的字节数以及对应的物理地址。物理地址必须是以64k为边界的。当然,还需要设置IRQ6,当数据传输结束的时候,控制器将发送一个IRQ6的中断。用DMA传输数据,这个过程是不需要占用CPU时间的,对于多任务的系统是比较好的选择。缺点是ISA DMA这种古老的DMA比PIO还要慢。
PIO数据传输既可以使用轮询,也能使用中断。使用PIO模式传输数据的速度比DMA传输快10%,但这会消耗CPU周期,是一个巨大的成本问题。然而,如果我们的操作系统或应用程序是单任务的,那么没有别的程序需要CPU,这样你就也可以使用PIO模式。使用PIO的要点是,在设置好了各种命令后,需要等待中断或者轮询状态结果,最后还需要使用IO的方式,读取数据,也就是这部分需要消耗额外的CPU周期。
值得注意的是,bochs并不支持PIO模式,使用PIO模式的时候bochs会报错,提示没有完全实现PIO模式。所以我在实验代码中也没有写PIO的代码。毕竟我的实验环境是bochs。

ISA DMA
ISA DMA全称Industry Standard Architecture Direct Memory Access,是一种古老的DMA方式,速度比PIO还要慢,但是编程相对于PCI BusMastering DMA要简单一点。这里我并不打算详细介绍ISA DMA,因为说明它需要的篇幅不亚于这篇。后面的代码中,在必要的地方我会加入一些解释。

3种模式
这三种模式是:PC-AT模式,PS/2模式,Mode 30模式。现在最有可能看到硬件上仍然运行模式是30模式。

FDC寄存器
floppy_regs

如上图所示,上面的寄存器我们不会都用到,一般情况下大概只会用到一半的样子,其他的我们可以暂时不去理会。另外我在这里不会列出每个寄存器的详细参数,因为这些参数很多而且复杂,不仅单调乏味,而且容易让初学者望而却步。我采取的做法是,先列出控制FDC的代码,然后在需要讲解的地方详细的说明。

实践
下面就是一段控制FDC读取软盘数据的代码。需要说明的是这份代码为了保持简洁易学,它没有任何的错误检测,另外代码也假设了你已经初始化了PIC,并且设置好了IRQ6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
; MASM的宏应该不陌生吧,就不做解释了。
outb macro port:req, b:req
mov dx, port
mov al, b
out dx, al
endm

inb macro port:req
mov dx, port
in al, dx
endm

wait_status macro
inb 3f4h
@@:
test al, 80h
jz @B
endm

; ISA DMA 初始化部分, 由于ISA DMA不是这篇文章的重点
; 所以这里只说明大概的用途。
outb 0dh, 0ffh ; 重置DMA控制器
outb 0ah, 6h ; 选择设置2号DMA通道
outb 0ch, 0ffh ; 重置Flipflop寄存器
outb 4h, 0h ; 设置DMA的物理地址,需要设置两次
outb 4h, 0f0h ; 我这里设置的是0xf000
outb 81h, 0h ; 设置地址24bit的高8bit为0,也就是0x00f000
outb 0ch, 0ffh ; 重置Flipflop寄存器
outb 5h, 0ffh ; 设置物理内存大小,其大小为Length-1
outb 5h, 0fh ; 我这里的内存大小是0x1000,所以设置为0xfff
outb 0ah, 2h ; 选择清除2号DMA通道

; FDC的初始化过程
outb 3f2h, 0h ; 1.重置数字输出寄存器
outb 3f2h, 0ch
call WaitIrq

mov ecx, 4
check_int:
wait_status
outb 3f5h, 8h ; 发送8号命令,该命令清除控制器触发的中断
wait_status ; 并且返回结果,这里重复4次,是为了清除4个软驱的状态
inb 3f5h
wait_status
inb 3f5h
wait_status
loop check_int

outb 3f7h, 0h ; 2.设置传输速度为500kb/s
wait_status
outb 3f5h, 3h ; 3.设置FDC里面三个时钟以及DMA。
wait_status
outb 3f5h, 0dfh
wait_status
outb 3f5h, 2h

outb 3f2h, 1ch ; 开启软驱电动机
wait_status
outb 3f5h, 7h ; 发送校验命令
wait_status
outb 3f5h, 0h ; 选择0号软驱
wait_status
outb 3f5h, 8h ; 发送清楚中断命令,获得结果
wait_status
inb 3f5h
wait_status
inb 3f5h
outb 3f2h, 4h ; 关闭电动机

; 接下来是软盘的读取操作
outb 3f2h, 1ch ; 开启电动机
wait_status
outb 3f5h, 0fh ; 4.发送0f寻道命令
wait_status
outb 3f5h, 0h
wait_status
outb 3f5h, 0h
wait_status
outb 3f5h, 8h ; 发送清楚中断命令,获得结果
wait_status
inb 3f5h
wait_status
inb 3f5h

; 设置ISA DMA为读取
outb 0ah, 6h
outb 0bh, 46h
outb 0ah, 2h

wait_status
outb 3f5h, 0e6h ; 5.发送读取扇区命令
wait_status
outb 3f5h, 0h ; 设置磁头和驱动器号
wait_status
outb 3f5h, 0h ; 设置磁道
wait_status
outb 3f5h, 0h ; 设置磁头
wait_status
outb 3f5h, 1h ; 设置扇区号
wait_status
outb 3f5h, 2h ; 设置扇区大小
wait_status
outb 3f5h, 8h ; 设置读取扇区数量
wait_status
outb 3f5h, 1bh ; 设置磁盘为3.5英寸
wait_status
outb 3f5h, 0ffh ; 设置读取长度,总是0xff

call WaitIrq

mov ecx, 7
loop_ret:
wait_status
inb 3f5h
loop loop_ret

wait_status
outb 3f5h, 8h ; 发送清楚中断命令,获得结果
wait_status
inb 3f5h
wait_status
inb 3f5h
outb 3f2h, 4h ; 关闭电动机

1.重置数字输出寄存器
这是一个只写寄存器,可以用来控制FDC,例如控制软驱电动机,重置控制器,选择目标软驱,设置数据模式,以下是他的详细参数
Bits 0-1
00 - 软驱 0
01 - 软驱 1
10 - 软驱 2
11 - 软驱 3
Bit 2 重置
0 - 重置控制器
1 - 开启控制器
Bit 3 模式
0 - IRQ 模式
1 - DMA 模式
Bits 4 - 7 电动机控制器 (软驱 0 - 3)
0 - 停止电动机
1 - 开启电动机

我这里设置的是0Ch也就是说,选择了0号软驱,开启了控制器和DMA模式。

2.设置传输速度为500kb/s
这个寄存器只有前两位有效,下面两位不同的组合表达了不同的速度,如下表:
00 500 Kbps
10 250 Kbps
01 300 Kbps
11 1 Mbps

3.设置FDC里面三个时钟以及DMA
三个时钟分别是步进速率时钟、磁头卸载时钟和磁头装入时钟。数据格式如下:
S S S S H H H H - S=步进速率时钟 H=磁头卸载时钟
H H H H H H H NDMA - H=磁头装入时钟 NDMA=0 (DMA模式) 或者 1 (非DMA模式)
实际上这个大家可以随意设置,我设置的是步进速率时钟=3ms, 磁头卸载时钟=240ms, 磁头装入时钟=16ms

4.发送寻道命令进行寻道
寻道命令的参数是两个自己,分别代表磁头、柱面和驱动器号,格式如下
x x x x x HD DR1 DR0 - HD=磁头 DR1/DR0 = 驱动器
C C C C C C C C - C=柱面

5.发送读取扇区命令
读取和写入命令一样,有8个参数和7个返回值!
第1个参数字节 = (磁头号 << 2) | 驱动器号 (驱动器号必须是当前选择的驱动器)
第2个参数字节 = 柱面号
第3个参数字节 = 磁头号 (没错,重复了 = =)
第4个参数字节 = 开始的扇区号
第5个参数字节 = 2 (总是2,代表软盘扇区大小总是512字节)
第6个参数字节 = 操作扇区数
第7个参数字节 = 0x1b (软盘大小是3.5in)
第8个参数字节 = 0xff (总是0xff)
这里不关心返回值,如果有兴趣可以自己查找资料。

读取就是这样,如果要写入安排,只需要更改命令即可,这里就不再赘述了。最后按照国际惯例,提供一些深入学习的链接:
http://wiki.osdev.org/Floppy
http://en.wikipedia.org/wiki/Floppy
http://www.isdaman.com/alsos/hardware/fdc/floppy.htm
http://www.osdever.net/documents/82077AA_FloppyControllerDatasheet.pdf

MiniKernel

使用Windbg Logexts监控程序API调用

在我们调试BUG和逆向程序的时候,往往需要监控一些API的调用。这个时候我们可以借助调试器和第三方工具完成这样的任务。例如调试器可以下断点查看栈状态得到相关信息,或者使用第三方工具如API Monitor可以方便的监控API的调用。不过,这篇文章中想说的是另一个工具,Windbg的扩展程序Logexts.dll。闲话少说,我们先看一看监控的效果如何吧。

20130914230701

上图是Windbg输出的信息,再看看使用logviewer.exe查看的效果。

20130914234812

怎么样,是不是相当的不错!那么下面我就来简单介绍一下,这个扩展的用法。

!logexts.logi
将Logger注入目标程序,初始化监控,但是并不开启它。

!logexts.loge
开启监控,如果之前没有调用logexts.logi,这个扩展命令会先初始化监控,然后启动。

!logexts.logd
停止监控。这个命令会摘掉所有的Hook,从而让程序自由运行。不过COM的Hook并不会被摘除。

!logexts.logo
显示或者修改输出选项,这里有三种输出方式:1.在调试器中显示,2.输出到一个文本文件,3.输出到lgv文件。其中lgv文件会包含更多的信息,我们可以使用LogViewer进行查看。

!logexts.logc
显示或者控制监控的API分类。

!logexts.logb
显示或者刷新输出缓存。由于如果在监控过程中发生异常,那么扩展可能无法将记录的日志写入文件中,这个时候我们就需要这个命令,手动的将缓存中的数据写入文件。

!logexts.logm
显示和创建模块的包含/排除列表。这可以帮助我们指定记录那些特定模块中的API调用。

上面图中我所使用的命令是这样的:

1
2
3
4
5
>!logexts.loge D:\  
!logexts.logc d *
!logexts.logc e 15 16 19
!logexts.logo e *
!logexts.logm i notepad.exe

其输出结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
!logexts.loge D:\  
Windows API Logging Extensions v3.01
Parsing the manifest files...
Location: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\winext\manifest\main.h
Parsing file "main.h" ...
Parsing file "winerror.h" ...
Parsing file "kernel32.h" ...
Parsing file "debugging.h" ...
Parsing file "processes.h" ...
Parsing file "memory.h" ...
Parsing file "registry.h" ...
Parsing file "fileio.h" ...
Parsing file "strings.h" ...
Parsing file "user32.h" ...
Parsing file "clipboard.h" ...
Parsing file "hook.h" ...
Parsing file "gdi32.h" ...
Parsing file "winspool.h" ...
Parsing file "version.h" ...
Parsing file "winsock2.h" ...
Parsing file "advapi32.h" ...
Parsing file "uuids.h" ...
Parsing file "com.h" ...
Parsing file "shell.h" ...
Parsing file "ole32.h" ...
Parsing file "ddraw.h" ...
Parsing file "winmm.h" ...
Parsing file "avifile.h" ...
Parsing file "dplay.h" ...
Parsing file "d3d.h" ...
Parsing file "d3dtypes.h" ...
Parsing file "d3dcaps.h" ...
Parsing file "d3d8.h" ...
Parsing file "d3d8types.h" ...
Parsing file "d3d8caps.h" ...
Parsing file "dsound.h" ...
Parsing completed.
Logexts injected. Output: "D:\\LogExts\"
Logging enabled.
0:000> !logexts.logc d *
All categories disabled.
0:000> !logexts.logc e 15 16 19
15 IOFunctions Enabled
16 MemoryManagementFunctions Enabled
19 ProcessesAndThreads Enabled
0:000> !logexts.logo e *
Debugger Enabled
Text file Enabled
Verbose log Enabled
0:000> !logexts.logm i notepad.exe
Included modules:
notepad.exe

简单说明一下这些命令:
!logexts.loge D:\
设置log的保持路径,并且开启监控

!logexts.logc d *
先关闭所有API分类的监控

!logexts.logc e 15 16 19
然后设置我们想监控分类,这里是IOFunctions,MemoryManagementFunctions和ProcessesAndThreads。至于如何查询分类对于的id,可以直接输入!logexts.logc进行查看。

!logexts.logo e *
这里我开启了所有输出方式,注意:如果想要被监控的程序响应的更快,可以去掉Debugger的输出,因为显示花费的时间比较的多。

!logexts.logm i notepad.exe
最后当然是设置inclusion list了。

按下F5,让程序跑起来看看效果吧。先别着急惊叹,Logexts还有更惊艳的地方。那就是他的高度可配置性。如果你想监控他描述以外的API,那么你可以自己写这个API的“头文件”。这里用引号是因为,它并不是真正的头文件,只不过他的语法和C的头文件非常的相似。我们可以看一个例子:

创建%windbg_dir%\winext\manifest\Context.h
并且写入这些内容

1
2
3
4
5
6
7
8

category ActivationContext:
module KERNEL32.DLL:

FailOnFalse ActivateActCtx(HANDLE hActCtx, [out] PULONG_PTR lpCookie);
FailOnFalse DeactivateActCtx(DWORD dwFlags, ULONG_PTR upCookie);


在%windbg_dir%\winext\manifest\main.h文件的最后加入一行 #include “Context.h”

保存后,重启调试程序,输入!logexts.logc,可以看了多出了ActivationContext这一项。现在就可以选择这一项分类来监控ActivateActCtx和DeactivateActCtx了。

最后,大家应该发现了这样一个问题,开启这个API监控还是比较麻烦的,需要输入好几条命令。为了更方便的使用这个功能,我写了一个脚本来解决这个问题,这样就可以用一行命令来开启监控。使用方法是:
Usage $$>aa<logger.wds “d:\” “15 16 19” “notepad.exe”

下载logger

关于Logexts的更多详细信息请参考msdn:http://msdn.microsoft.com/en-us/library/windows/hardware/ff560170(v=vs.85).aspx.aspx)

DebuggingTips