首页
社区
课程
招聘
[原创]NdisWrapper框架分析
发表于: 2011-12-27 11:12 5785

[原创]NdisWrapper框架分析

2011-12-27 11:12
5785

boywhp@gmail.com
www.opscn.com

ndiswrapper简介:
一些usb无线网卡厂商只提供windows下的驱动,那么你可以直接使用ndiswrapper来执行windows上的网卡驱动,是不是很cool呢?其实也算是linux不如windows流行的一个证据。

ndiswrapper项目主页:
http://sourceforge.net/apps/mediawiki/ndiswrapper/index.php?title=Main_Page

入口:wrapper.c
module_init(wrapper_init);
        if (wrapmem_init() || ntoskernel_init() || ndis_init() ||
            wrapndis_init() || usb_init() || wrap_procfs_init() ||
            loader_init())
完成各个子模块初始化工作。
重点在loader.c        int loader_init(void)

该函数首先misc_register创建一个杂项设备,然后调用register_devices注册设备
static void register_devices(void),该函数会依次注册PCI以及USB驱动
pci_register_driver(&wrap_pci_driver);
usb_register(&wrap_usb_driver);

以USB为例:
static struct usb_driver wrap_usb_driver = {
        .name = DRIVER_NAME,
        .id_table = wrap_usb_id_table,
        .probe = wrap_pnp_start_usb_device,
        .disconnect = __devexit_p(wrap_pnp_remove_usb_device),
        .suspend = wrap_pnp_suspend_usb_device,
        .resume = wrap_pnp_resume_usb_device,
};

首先看usb设备探测处理,pnp.c wrap_pnp_start_usb_device

load_device.bus = WRAP_USB_BUS;
load_device.vendor = le16_to_cpu(udev->descriptor.idVendor);
load_device.device = le16_to_cpu(udev->descriptor.idProduct);
load_device.subvendor = 0;
load_device.subdevice = 0;
wd = load_wrap_device(&load_device);
成功后调用ret = wrap_pnp_start_device(wd);启动设备

loader.c
struct wrap_device *load_wrap_device(struct load_device *load_device)
char *argv[] = {"loadndisdriver", WRAP_CMD_LOAD_DEVICE,
#if DEBUG >= 1
                "1",
#else
                "0",
#endif
        UTILS_VERSION, vendor, device,
        subvendor, subdevice, bus, NULL};
        char *env[] = {NULL};
ret = call_usermodehelper("/sbin/loadndisdriver", argv, env, 1);
可见ndiswrapper会通过call_usermodehelper来处理驱动的加载,主要是需要在ring3下做一些参数配置之类的东西,loadndisdriver通过ioctl接口同内核模块通信。

具体实现在loader.c wrapper_ioctl函数
static int wrapper_ioctl(struct inode *inode, struct file *file,
                         unsigned int cmd, unsigned long arg)

以WRAP_IOCTL_LOAD_DRIVER为例,我们看ndiswrapper是如何加载windows驱动的。

case WRAP_IOCTL_LOAD_DRIVER:
        load_driver = vmalloc(sizeof(*load_driver));
        if (copy_from_user(load_driver, addr, sizeof(*load_driver)))
                ret = -EFAULT;
        else
                ret = load_user_space_driver(load_driver);

首先从R3程序复制参数,然后调用load_user_space_driver

loader.c load_user_space_driver
static int load_user_space_driver(struct load_driver *load_driver)

        drv_obj = allocate_object(sizeof(*drv_obj), OBJECT_TYPE_DRIVER, NULL);
        drv_obj->drv_ext = kzalloc(sizeof(*(drv_obj->drv_ext)), GFP_KERNEL);

        InitializeListHead(&drv_obj->drv_ext->custom_ext);
        IoAllocateDriverObjectExtension(drv_obj,
                                            (void *)WRAP_DRIVER_CLIENT_ID,
                                            sizeof(*wrap_driver),
                                            (void **)&wrap_driver);

        memset(wrap_driver, 0, sizeof(*wrap_driver));
        InitializeListHead(&wrap_driver->list);
        InitializeListHead(&wrap_driver->settings);

        wrap_driver->drv_obj = drv_obj;
        RtlInitAnsiString(&ansi_reg, "/tmp");
        RtlAnsiStringToUnicodeString(&drv_obj->name, &ansi_reg, TRUE);

        strncpy(wrap_driver->name, load_driver->name, sizeof(wrap_driver->name));
        wrap_driver->name[sizeof(wrap_driver->name)-1] = 0;

        if (load_sys_files(wrap_driver, load_driver) ||
            load_bin_files_info(wrap_driver, load_driver) ||
            load_settings(wrap_driver, load_driver) ||
            start_wrap_driver(wrap_driver) ||
            add_wrap_driver(wrap_driver)) {
该函数完成windows的ndis驱动实际加载以及启动。

loader.c load_sys_files
static int load_sys_files(struct wrap_driver *driver, struct load_driver *load_driver)
该函数首先申请内存,将待加载的sys pe文件复制到申请的内存。
然后link_pe_images(driver->pe_images, driver->num_pe_images)

loader.c link_pe_images
int link_pe_images(struct pe_image *pe_image, unsigned short n)
将依次执行
check_nt_hdr                         执行一些检查
fix_pe_image                         执行内存节对齐操作
read_exports                        如果出函数,添加到全局函数表
fixup_reloc                                处理重定位数据修改
fixup_imports                        处理导入表
fix_user_shared_data_addr         X64的一些处理
至此windows的ndis二进制镜像正式加载到linux内核。

以fixup_imports为例,看一下ndiswrapper是如何处理NDIS 接口的。
pe_linker.c fixup_imports
static int fixup_imports(void *image, IMAGE_NT_HEADERS *nt_hdr)

        opt_hdr = &nt_hdr->OptionalHeader;
        import_data_dir =
                &opt_hdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
        dirent = RVA2VA(image, import_data_dir->VirtualAddress,
                        IMAGE_IMPORT_DESCRIPTOR *);

        for (i = 0; dirent[i].Name; i++) {
                name = RVA2VA(image, dirent[i].Name, char*);

                ret += import(image, &dirent[i], name);
        }

static int import(void *image, IMAGE_IMPORT_DESCRIPTOR *dirent, char *dll)
        lookup_tbl = RVA2VA(image, dirent->u.OriginalFirstThunk, ULONG_PTR *);
        address_tbl = RVA2VA(image, dirent->FirstThunk, ULONG_PTR *);

        for (i = 0; lookup_tbl[i]; i++) {
                symname = RVA2VA(image,
                                 ((lookup_tbl[i] &
                                ~IMAGE_ORDINAL_FLAG) + 2), char *);

                get_export(symname, &adr);
                address_tbl[i] = (ULONG_PTR)adr;
        }
定位到pe的导入函数表,然后依次通过get_export函数查询函数的地址,然后修改地址表。

static int get_export(char *name, generic_func *func)
{
        int i, j;

        struct wrap_export *exports[] = {
                ntoskernel_exports,
                ntoskernel_io_exports,
                ndis_exports,
                crt_exports,
                hal_exports,
                rtl_exports,
#ifdef ENABLE_USB
                usb_exports,
#endif
        };

        for (j = 0; j < ARRAY_SIZE(exports); j++)
                for (i = 0; exports[j][i].name != NULL; i++)
                        if (strcmp(exports[j][i].name, name) == 0) {
                                *func = exports[j][i].func;
                                return 0;
                        }

        for (i = 0; i < num_pe_exports; i++)
                if (strcmp(pe_exports[i].name, name) == 0) {
                        *func = pe_exports[i].addr;
                        return 0;
                }

        return -1;
}
这个函数依次检查所有的导出函数表,以及pe_exports【sys pe镜像导出函数表】返回函数地址。

以ndis_exports表为例;
#include "ndis_exports.h"  
该文件通过mkexport.sh脚本自动生成 ./mkexport.sh ndis.c ndis_exports.h
内容如下:
extern struct wrap_export ndis_export[];
struct wrap_export ndis_export[] = {
        WIN_SYMBOL(NdisAcquireReadWriteLock, 3),
        WIN_SYMBOL(NdisAcquireSpinLock, 1),

其中wrap_export数据结构以及WIN_SYMBOL X86宏定义如下:
struct wrap_export {
        const char *name;
        generic_func func;
};

#define WIN_SYMBOL(name, argc) {#name, (generic_func)name}

WIN_SYMBOL(NdisAcquireReadWriteLock, 3),
即对应{“NdisAcquireReadWriteLock”, NdisAcquireReadWriteLock}

至此整个ndiswrapper的框架大致出来了,最关键的部分NDIS API实现见ndis.c,实现了NDIS导出的133个函数。
在此向作者表示敬意,全世界的linux用户感谢你们的辛勤劳动!

2011-12-27


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (1)
雪    币: 209
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
“其实也算是linux不如windows流行的一个证据”应该改为“其实也算是linux在桌面系统领域不如windows流行的一个证据”,而且没Linux驱动的网卡都是些2流的卡!
2012-2-24 20:34
0
游客
登录 | 注册 方可回帖
返回
//