freebsd-skq/sys/compat/ndis/subr_pe.c
Bill Paul c854fc1092 Commit the first cut of Project Evil, also known as the NDISulator.
Yes, it's what you think it is. Yes, you should run away now.

This is a special compatibility module for allowing Windows NDIS
miniport network drivers to be used with FreeBSD/x86. This provides
_binary_ NDIS compatibility (not source): you can run NDIS driver
code, but you can't build it. There are three main parts:

sys/compat/ndis: the NDIS compat API, which provides binary
compatibility functions for many routines in NDIS.SYS, HAL.dll
and ntoskrnl.exe in Windows (these are the three modules that
most NDIS miniport drivers use). The compat module also contains
a small PE relocator/dynalinker which relocates the Windows .SYS
image and then patches in our native routines.

sys/dev/if_ndis: the if_ndis driver wrapper. This module makes
use of the ndis compat API and can be compiled with a specially
prepared binary image file (ndis_driver_data.h) containing the
Windows .SYS image and registry key information parsed out of the
accompanying .INF file. Once if_ndis.ko is built, it can be loaded
and unloaded just like a native FreeBSD kenrel module.

usr.sbin/ndiscvt: a special utility that converts foo.sys and foo.inf
into an ndis_driver_data.h file that can be compiled into if_ndis.o.
Contains an .inf file parser graciously provided by Matt Dodd (and
mercilessly hacked upon by me) that strips out device ID info and
registry key info from a .INF file and packages it up with a binary
image array. The ndiscvt(8) utility also does some manipulation of
the segments within the .sys file to make life easier for the kernel
loader. (Doing the manipulation here saves the kernel code from having
to move things around later, which would waste memory.)

ndiscvt is only built for the i386 arch. Only files.i386 has been
updated, and none of this is turned on in GENERIC. It should probably
work on pc98. I have no idea about amd64 or ia64 at this point.

This is still a work in progress. I estimate it's about %85 done, but
I want it under CVS control so I can track subsequent changes. It has
been tested with exactly three drivers: the LinkSys LNE100TX v4 driver
(Lne100v4.sys), the sample Intel 82559 driver from the Windows DDK
(e100bex.sys) and the Broadcom BCM43xx wireless driver (bcmwl5.sys). It
still needs to have a net80211 stuff added to it. To use it, you would
do something like this:

# cd /sys/modules/ndis
# make; make load
# cd /sys/modules/if_ndis
# ndiscvt -i /path/to/foo.inf -s /path/to/foo.sys -o ndis_driver_data.h
# make; make load
# sysctl -a | grep ndis

All registry keys are mapped to sysctl nodes. Sometimes drivers refer
to registry keys that aren't mentioned in foo.inf. If this happens,
the NDIS API module creates sysctl nodes for these keys on the fly so
you can tweak them.

An example usage of the Broadcom wireless driver would be:

# sysctl hw.ndis0.EnableAutoConnect=1
# sysctl hw.ndis0.SSID="MY_SSID"
# sysctl hw.ndis0.NetworkType=0 (0 for bss, 1 for adhoc)
# ifconfig ndis0 <my ipaddr> netmask 0xffffff00 up

Things to be done:

- get rid of debug messages
- add in ndis80211 support
- defer transmissions until after a status update with
  NDIS_STATUS_CONNECTED occurs
- Create smarter lookaside list support
- Split off if_ndis_pci.c and if_ndis_pccard.c attachments
- Make sure PCMCIA support works
- Fix ndiscvt to properly parse PCMCIA device IDs from INF files
- write ndisapi.9 man page
2003-12-11 22:34:37 +00:00

538 lines
14 KiB
C

/*
* Copyright (c) 2003
* Bill Paul <wpaul@windriver.com>. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Bill Paul.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* This file contains routines for relocating and dynamically linking
* executable object code files in the Windows(r) PE (Portable Executable)
* format. In Windows, anything with a .EXE, .DLL or .SYS extention is
* considered an executable, and all such files have some structures in
* common. The PE format was apparently based largely on COFF but has
* mutated significantly over time. We are mainly concerned with .SYS files,
* so this module implements only enough routines to be able to parse the
* headers and sections of a .SYS object file and perform the necessary
* relocations and jump table patching to allow us to call into it
* (and to have it call back to us). Note that while this module
* can handle fixups for imported symbols, it knows nothing about
* exporting them.
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#ifdef _KERNEL
#include <sys/systm.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#endif
#include <compat/ndis/pe_var.h>
static u_int32_t pe_functbl_match(image_patch_table *, char *);
/*
* Check for an MS-DOS executable header. All Windows binaries
* have a small MS-DOS executable prepended to them to print out
* the "This program requires Windows" message. Even .SYS files
* have this header, in spite of the fact that you're can't actually
* run them directly.
*/
int
pe_get_dos_header(imgbase, hdr)
vm_offset_t imgbase;
image_dos_header *hdr;
{
uint16_t signature;
if (imgbase == NULL || hdr == NULL)
return (EINVAL);
signature = *(uint16_t *)imgbase;
if (signature != IMAGE_DOS_SIGNATURE)
return (ENOEXEC);
bcopy ((char *)imgbase, (char *)hdr, sizeof(image_dos_header));
return(0);
}
/*
* Verify that this image has a Windows NT PE signature.
*/
int
pe_is_nt_image(imgbase)
vm_offset_t imgbase;
{
uint32_t signature;
image_dos_header *dos_hdr;
if (imgbase == NULL)
return (EINVAL);
signature = *(uint16_t *)imgbase;
if (signature == IMAGE_DOS_SIGNATURE) {
dos_hdr = (image_dos_header *)imgbase;
signature = *(uint32_t *)(imgbase + dos_hdr->idh_lfanew);
if (signature == IMAGE_NT_SIGNATURE)
return(0);
}
return(ENOEXEC);
}
/*
* Return a copy of the optional header. This contains the
* executable entry point and the directory listing which we
* need to find the relocations and imports later.
*/
int
pe_get_optional_header(imgbase, hdr)
vm_offset_t imgbase;
image_optional_header *hdr;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
if (imgbase == NULL || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
dos_hdr = (image_dos_header *)(imgbase);
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
bcopy ((char *)&nt_hdr->inh_optionalhdr, (char *)hdr,
sizeof(image_optional_header));
return(0);
}
/*
* Return a copy of the file header. Contains the number of
* sections in this image.
*/
int
pe_get_file_header(imgbase, hdr)
vm_offset_t imgbase;
image_file_header *hdr;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
if (imgbase == NULL || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
bcopy ((char *)&nt_hdr->inh_filehdr, (char *)hdr,
sizeof(image_file_header));
return(0);
}
/*
* Return the header of the first section in this image (usually
* .text).
*/
int
pe_get_section_header(imgbase, hdr)
vm_offset_t imgbase;
image_section_header *hdr;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
image_section_header *sect_hdr;
if (imgbase == NULL || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
sect_hdr = (image_section_header *)((vm_offset_t)nt_hdr +
sizeof(image_nt_header));
bcopy ((char *)sect_hdr, (char *)hdr, sizeof(image_section_header));
return(0);
}
/*
* Return the number of sections in this executable, or 0 on error.
*/
int
pe_numsections(imgbase)
vm_offset_t imgbase;
{
image_file_header file_hdr;
if (pe_get_file_header(imgbase, &file_hdr))
return(0);
return (file_hdr.ifh_numsections);
}
/*
* Return the base address that this image was linked for.
* This helps us calculate relocation addresses later.
*/
vm_offset_t
pe_imagebase(imgbase)
vm_offset_t imgbase;
{
image_optional_header optional_hdr;
if (pe_get_optional_header(imgbase, &optional_hdr))
return(0);
return (optional_hdr.ioh_imagebase);
}
/*
* Return the offset of a given directory structure within the
* image. Directories reside within sections.
*/
vm_offset_t
pe_directory_offset(imgbase, diridx)
vm_offset_t imgbase;
uint32_t diridx;
{
image_optional_header opt_hdr;
vm_offset_t dir;
if (pe_get_optional_header(imgbase, &opt_hdr))
return(0);
if (diridx >= opt_hdr.ioh_rva_size_cnt)
return(0);
dir = opt_hdr.ioh_datadir[diridx].idd_vaddr;
return(pe_translate_addr(imgbase, dir));
}
vm_offset_t
pe_translate_addr(imgbase, rva)
vm_offset_t imgbase;
uint32_t rva;
{
image_optional_header opt_hdr;
image_section_header *sect_hdr;
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
int i = 0, sections, fixedlen;
if (pe_get_optional_header(imgbase, &opt_hdr))
return(0);
sections = pe_numsections(imgbase);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
sect_hdr = (image_section_header *)((vm_offset_t)nt_hdr +
sizeof(image_nt_header));
/*
* The test here is to see if the RVA falls somewhere
* inside the section, based on the section's start RVA
* and its length. However it seems sometimes the
* virtual length isn't enough to cover the entire
* area of the section. We fudge by taking into account
* the section alignment and rounding the section length
* up to a page boundary.
*/
while (i++ < sections) {
fixedlen = sect_hdr->ish_misc.ish_vsize;
fixedlen += ((opt_hdr.ioh_sectalign - 1) -
sect_hdr->ish_misc.ish_vsize) &
(opt_hdr.ioh_sectalign - 1);
if (sect_hdr->ish_vaddr <= (u_int32_t)rva &&
(sect_hdr->ish_vaddr + fixedlen) >
(u_int32_t)rva)
break;
sect_hdr++;
}
if (i > sections)
return(0);
return((vm_offset_t)(imgbase + rva - sect_hdr->ish_vaddr +
sect_hdr->ish_rawdataaddr));
}
/*
* Get the section header for a particular section. Note that
* section names can be anything, but there are some standard
* ones (.text, .data, .rdata, .reloc).
*/
int
pe_get_section(imgbase, hdr, name)
vm_offset_t imgbase;
image_section_header *hdr;
const char *name;
{
image_dos_header *dos_hdr;
image_nt_header *nt_hdr;
image_section_header *sect_hdr;
int i, sections;
if (imgbase == NULL || hdr == NULL)
return(EINVAL);
if (pe_is_nt_image(imgbase))
return (EINVAL);
sections = pe_numsections(imgbase);
dos_hdr = (image_dos_header *)imgbase;
nt_hdr = (image_nt_header *)(imgbase + dos_hdr->idh_lfanew);
sect_hdr = (image_section_header *)((vm_offset_t)nt_hdr +
sizeof(image_nt_header));
for (i = 0; i < sections; i++) {
if (!strcmp ((char *)&sect_hdr->ish_name, name)) {
bcopy((char *)sect_hdr, (char *)hdr,
sizeof(image_section_header));
return(0);
} else
sect_hdr++;
}
return (ENOEXEC);
}
/*
* Apply the base relocations to this image. The relocation table
* resides within the .reloc section. Relocations are specified in
* blocks which refer to a particular page. We apply the relocations
* one page block at a time.
*/
int
pe_relocate(imgbase)
vm_offset_t imgbase;
{
image_section_header sect;
image_base_reloc *relhdr;
uint16_t rel, *sloc;
uint32_t base, delta, *lloc;
int i, count;
vm_offset_t txt;
base = pe_imagebase(imgbase);
pe_get_section(imgbase, &sect, ".text");
txt = pe_translate_addr(imgbase, sect.ish_vaddr);
delta = (uint32_t)(txt) - base - sect.ish_vaddr;
pe_get_section(imgbase, &sect, ".reloc");
relhdr = (image_base_reloc *)(imgbase + sect.ish_rawdataaddr);
do {
count = (relhdr->ibr_blocksize -
(sizeof(uint32_t) * 2)) / sizeof(uint16_t);
for (i = 0; i < count; i++) {
rel = relhdr->ibr_rel[i];
switch (IMR_RELTYPE(rel)) {
case IMAGE_REL_BASED_ABSOLUTE:
break;
case IMAGE_REL_BASED_HIGHLOW:
lloc = (uint32_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*lloc = pe_translate_addr(imgbase,
(*lloc - base));
break;
case IMAGE_REL_BASED_HIGH:
sloc = (uint16_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*sloc += (delta & 0xFFFF0000) >> 16;
break;
case IMAGE_REL_BASED_LOW:
sloc = (uint16_t *)pe_translate_addr(imgbase,
relhdr->ibr_vaddr + IMR_RELOFFSET(rel));
*sloc += (delta & 0xFFFF);
break;
default:
printf ("[%d]reloc type: %d\n",i,
IMR_RELTYPE(rel));
break;
}
}
relhdr = (image_base_reloc *)((vm_offset_t)relhdr +
relhdr->ibr_blocksize);
} while (relhdr->ibr_blocksize);
return(0);
}
/*
* Return the import descriptor for a particular module. An image
* may be linked against several modules, typically HAL.dll, ntoskrnl.exe
* and NDIS.SYS. For each module, there is a list of imported function
* names and their addresses.
*/
int
pe_get_import_descriptor(imgbase, desc, module)
vm_offset_t imgbase;
image_import_descriptor *desc;
char *module;
{
vm_offset_t offset;
image_import_descriptor *imp_desc;
char *modname;
if (imgbase == NULL || module == NULL || desc == NULL)
return(EINVAL);
offset = pe_directory_offset(imgbase, IMAGE_DIRECTORY_ENTRY_IMPORT);
if (offset == 0)
return (ENOENT);
imp_desc = (void *)offset;
while (imp_desc->iid_nameaddr) {
modname = (char *)pe_translate_addr(imgbase,
imp_desc->iid_nameaddr);
if (!strncmp(module, modname, strlen(module))) {
bcopy((char *)imp_desc, (char *)desc,
sizeof(image_import_descriptor));
return(0);
}
imp_desc++;
}
return (ENOENT);
}
/*
* Find the function that matches a particular name. This doesn't
* need to be particularly speedy since it's only run when loading
* a module for the first time.
*/
static vm_offset_t
pe_functbl_match(functbl, name)
image_patch_table *functbl;
char *name;
{
image_patch_table *p;
if (functbl == NULL || name == NULL)
return(0);
p = functbl;
while (p->ipt_name != NULL) {
if (!strcmp(p->ipt_name, name))
return((uint32_t)p->ipt_func);
p++;
}
printf ("no match for %s\n", name);
return((vm_offset_t)p->ipt_func);
}
/*
* Patch the imported function addresses for a given module.
* The caller must specify the module name and provide a table
* of function pointers that will be patched into the jump table.
* Note that there are actually two copies of the jump table: one
* copy is left alone. In a .SYS file, the jump tables are usually
* merged into the INIT segment.
*
* Note: Windows uses the _stdcall calling convention. This means
* that the callback functions provided in the function table must
* be declared using __attribute__((__stdcall__)), otherwise the
* Windows code will likely screw up the %esp register and cause
* us to jump to an invalid address when it returns.
*/
int
pe_patch_imports(imgbase, module, functbl)
vm_offset_t imgbase;
char *module;
image_patch_table *functbl;
{
image_import_descriptor imp_desc;
char *fname;
vm_offset_t *nptr, *fptr;
vm_offset_t func;
if (imgbase == NULL || module == NULL || functbl == NULL)
return(EINVAL);
if (pe_get_import_descriptor(imgbase, &imp_desc, module))
return(ENOEXEC);
nptr = (vm_offset_t *)pe_translate_addr(imgbase,
imp_desc.iid_import_name_table_addr);
fptr = (vm_offset_t *)pe_translate_addr(imgbase,
imp_desc.iid_import_address_table_addr);
while (nptr != NULL && pe_translate_addr(imgbase, *nptr) != NULL) {
fname = (char *)pe_translate_addr(imgbase, (*nptr) + 2);
func = pe_functbl_match(functbl, fname);
if (func)
*fptr = func;
#ifdef notdef
if (*fptr == 0)
return(ENOENT);
#endif
nptr++;
fptr++;
}
return(0);
}