Low-level Implementation
Details of Wine's Low-level Implementation...
Builtin DLLs
Written by &name-juergen-schmied; &email-juergen-schmied;
(Extracted from wine/documentation/internal-dll)
This document describes some points you should know before
implementing the internal counterparts to external DLL's.
Only 32 bit DLL's are considered.
1. The LibMain function
This is the way to do some initializing when a process or
thread is attached to the DLL. The function name is taken
from a *.spec file line:
init YourFunctionName
Then, you have to implement the function:
BOOL32 WINAPI YourLibMain(HINSTANCE32 hinstDLL,
DWORD fdwReason, LPVOID lpvReserved)
{ if (fdwReason==DLL_PROCESS_ATTACH)
{ ...
}
....
}
2. Using functions from other built-in DLL's
The problem here is, that you can't know if you have to call
the function from the internal or the external DLL. If you
just call the function you will get the internal
implementation. If the external DLL is loaded the executed
program will use the external DLL and you the internal one.
When you -as an example- fill an iconlist placed in the
internal DLL the application won't get the icons from the
external DLL.
To work around this, you should always use a pointer to call
such functions:
/* definition of the pointer type*/
void (CALLBACK* pDLLInitComctl)();
/* getting the function address this should be done in the
LibMain function when called with DLL_PROCESS_ATTACH*/
BOOL32 WINAPI Shell32LibMain(HINSTANCE32 hinstDLL, DWORD fdwReason,
LPVOID lpvReserved)
{ HINSTANCE32 hComctl32;
if (fdwReason==DLL_PROCESS_ATTACH)
{ /* load the external / internal DLL*/
hComctl32 = LoadLibrary32A("COMCTL32.DLL");
if (hComctl32)
{ /* get the function pointer */
pDLLInitComctl=GetProcAddress32(hComctl32,"InitCommonControlsEx");
/* check it */
if (pDLLInitComctl)
{ /* use it */
pDLLInitComctl();
}
/* free the DLL / decrease the ref count */
FreeLibrary32(hComctl32);
}
else
{ /* do some panic*/
ERR(shell,"P A N I C error getting functionpointers\n");
exit (1);
}
}
....
3. Getting resources from a *.rc file linked to the DLL
< If you know how, write some lines>
Accelerators
Findings researched by Uwe Bonnes, Ulrich Weigand and Marcus Meissner.
(Extracted from wine/documentation/accelerators)
Some notes concerning accelerators.
There are three differently sized
accelerator structures exposed to the user. The general layout
is:
BYTE fVirt;
WORD key;
WORD cmd;
We now have three different appearances:
Accelerators in NE resources. These have a size of 5 byte
and do not have any padding. This is also the internal
layout of the global handle HACCEL (16 and
32) in Windows 95 and Wine. Exposed to the user as Win16
global handles HACCEL16 and
HACCEL32 by the Win16/Win32 API.
Accelerators in PE resources. These have a size of 8 byte.
Layout is:
BYTE fVirt;
BYTE pad0;
WORD key;
WORD cmd;
WORD pad1;
They are exposed to the user only by direct accessing PE
resources.
Accelerators in the Win32 API. These have a size of 6
bytes. Layout is:
BYTE fVirt;
BYTE pad0;
WORD key;
WORD cmd;
These are exposed to the user by the
CopyAcceleratorTable and
CreateAcceleratorTable functions in
the Win32 API.
Why two types of accelerators in the Win32 API? We can only
guess, but my best bet is that the Win32 resource compiler
can/does not handle struct packing. Win32 ACCEL
is defined using #pragma(2) for the
compiler but without any packing for RC, so it will assume
#pragma(4).
File Handles
Written by (???)
(Extracted from wine/documentation/filehandles)
DOS treats the first 5 file handles as special cases. They
map directly to stdin,
stdout, stderr,
stdaux and stdprn.
Windows 16 inherits this behavior, and in fact, win16 handles
are interchangeable with DOS handles. Some nasty windows
programs even do this!
Windows32 issues file handles starting from
1, on the grounds that most GUI processes
don't need a stdin,
stdout, etc.
The Wine handle code is implemented in the Win32 style, and
the Win16 functions use two macros to convert to and from the
two types.
The macros are defined in file.h as follows:
#define HFILE16_TO_HFILE32(handle) \
(((handle)==0) ? GetStdHandle(STD_INPUT_HANDLE) : \
((handle)==1) ? GetStdHandle(STD_OUTPUT_HANDLE) : \
((handle)==2) ? GetStdHandle(STD_ERROR_HANDLE) : \
((handle)>0x400) ? handle : \
(handle)-5)
#define HFILE32_TO_HFILE16(handle) ({ HFILE32 hnd=handle; \
((hnd==HFILE_ERROR32) ? HFILE_ERROR16 : \
((handle>0x400) ? handle : \
(HFILE16)hnd+5); })
Be careful not to use the macro
HFILE16_TO_HFILE32 on functions with
side-effects, as it will cause them to be evaluated several
times. This could be considered a bug, but the use of this
macro is limited enough not to need a rewrite.
The 0x400 special case above deals with
LZW filehandles (see misc/lzexpand.c).
Doing A Hardware Trace In Wine
Written by &name-jonathan-buzzard; &email-jonathan-buzzard;
(Extracted from wine/documentation/ioport-trace-hints)
The primary reason to do this is to reverse engineer a
hardware device for which you don't have documentation, but
can get to work under Wine.
This lot is aimed at parallel port devices, and in particular
parallel port scanners which are now so cheap they are
virtually being given away. The problem is that few
manufactures will release any programming information which
prevents drivers being written for Sane, and the traditional
technique of using DOSemu to produce the traces does not work
as the scanners invariably only have drivers for Windows.
Please note that I have not been able to get my scanner
working properly (a UMAX Astra 600P), but a couple of people
have reported success with at least the Artec AS6e scanner. I
am not in the process of developing any driver nor do I intend
to, so don't bug me about it. My time is now spent writing
programs to set things like battery save options under Linux
on Toshiba laptops, and as such I don't have any spare time
for writing a driver for a parallel port scanner etc.
Presuming that you have compiled and installed wine the first
thing to do is is to enable direct hardware access to your
parallel port. To do this edit wine.conf
(usually in /usr/local/etc) and in the
ports section add the following two lines
read=0x378,0x379,0x37a,0x37c,0x77a
write=0x378,x379,0x37a,0x37c,0x77a
This adds the necessary access required for SPP/PS2/EPP/ECP
parallel port on LPT1. You will need to adjust these number
accordingly if your parallel port is on LPT2 or LPT0.
When starting wine use the following command line, where
XXXX is the program you need to run in
order to access your scanner, and YYYY is
the file your trace will be stored in:
wine -debugmsg +io XXXX 2> >(sed 's/^[^:]*:io:[^ ]* //' > YYYY)
You will need large amounts of hard disk space (read hundreds
of megabytes if you do a full page scan), and for reasonable
performance a really fast processor and lots of RAM.
You might well find the log compression program that David
Campbell campbell@torque.net wrote helpful in
reducing the size of the log files. This can be obtained by
the following command:
sh ioport-trace-hints
This should extract shrink.c (which is
located at the end of this file. Compile the log compression
program by:
cc shrink.c -o shrink
Use the shrink program to reduce the
physical size of the raw log as follows:
cat log | shrink > log2
The trace has the basic form of
XXXX > YY @ ZZZZ:ZZZZ
where XXXX is the port in hexidecimal being
accessed, YY is the data written (or read)
from the port, and ZZZZ:ZZZZ is the address
in memory of the instruction that accessed the port. The
direction of the arrow indicates whether the data was written
or read from the port.
> data was written to the port
< data was read from the port
My basic tip for interpreting these logs is to pay close
attention to the addresses of the IO instructions. Their
grouping and sometimes proximity should reveal the presence of
subroutines in the driver. By studying the different versions
you should be able to work them out. For example consider the
following section of trace from my UMAX Astra 600P
0x378 > 55 @ 0297:01ec
0x37a > 05 @ 0297:01f5
0x379 < 8f @ 0297:01fa
0x37a > 04 @ 0297:0211
0x378 > aa @ 0297:01ec
0x37a > 05 @ 0297:01f5
0x379 < 8f @ 0297:01fa
0x37a > 04 @ 0297:0211
0x378 > 00 @ 0297:01ec
0x37a > 05 @ 0297:01f5
0x379 < 8f @ 0297:01fa
0x37a > 04 @ 0297:0211
0x378 > 00 @ 0297:01ec
0x37a > 05 @ 0297:01f5
0x379 < 8f @ 0297:01fa
0x37a > 04 @ 0297:0211
0x378 > 00 @ 0297:01ec
0x37a > 05 @ 0297:01f5
0x379 < 8f @ 0297:01fa
0x37a > 04 @ 0297:0211
0x378 > 00 @ 0297:01ec
0x37a > 05 @ 0297:01f5
0x379 < 8f @ 0297:01fa
0x37a > 04 @ 0297:0211
As you can see there is a repeating structure starting at
address 0297:01ec that consists of four io
accesses on the parallel port. Looking at it the first io
access writes a changing byte to the data port the second
always writes the byte 0x05 to the control
port, then a value which always seems to
0x8f is read from the status port at which
point a byte 0x04 is written to the control
port. By studying this and other sections of the trace we can
write a C routine that emulates this, shown below with some
macros to make reading/writing on the parallel port easier to
read.
#define r_dtr(x) inb(x)
#define r_str(x) inb(x+1)
#define r_ctr(x) inb(x+2)
#define w_dtr(x,y) outb(y, x)
#define w_str(x,y) outb(y, x+1)
#define w_ctr(x,y) outb(y, x+2)
/*
* Seems to be sending a command byte to the scanner
*
*/
int udpp_put(int udpp_base, unsigned char command)
{
int loop,value;
w_dtr(udpp_base, command);
w_ctr(udpp_base, 0x05);
for (loop=0;loop<10;loop++)
if (((value=r_str(udpp_base)) & 0x80)!=0x00) {
w_ctr(udpp_base, 0x04);
return value & 0xf8;
}
return (value & 0xf8) | 0x01;
}
For the UMAX Astra 600P only seven such routines exist (well
14 really, seven for SPP and seven for EPP). Whether you
choose to disassemble the driver at this point to verify the
routines is your own choice. If you do, the address from the
trace should help in locating them in the disassembly.
You will probably then find it useful to write a script/perl/C
program to analyse the logfile and decode them futher as this
can reveal higher level grouping of the low level routines.
For example from the logs from my UMAX Astra 600P when decoded
further reveal (this is a small snippet)
start:
put: 55 8f
put: aa 8f
put: 00 8f
put: 00 8f
put: 00 8f
put: c2 8f
wait: ff
get: af,87
wait: ff
get: af,87
end: cc
start:
put: 55 8f
put: aa 8f
put: 00 8f
put: 03 8f
put: 05 8f
put: 84 8f
wait: ff
From this it is easy to see that put
routine is often grouped together in five successive calls
sending information to the scanner. Once these are understood
it should be possible to process the logs further to show the
higher level routines in an easy to see format. Once the
highest level format that you can derive from this process is
understood, you then need to produce a series of scans varying
only one parameter between them, so you can discover how to
set the various parameters for the scanner.
The following is the shrink.c program.
cat > shrink.c <<EOF
#include <stdio.h>
#include <string.h>
void
main (void)
{
char buff[256], lastline[256];
int count;
count = 0;
lastline[0] = 0;
while (!feof (stdin))
{
fgets (buff, sizeof (buff), stdin);
if (strcmp (buff, lastline) == 0)
{
count++;
}
else
{
if (count > 1)
fprintf (stdout, "# Last line repeated %i times #\n", count);
fprintf (stdout, "%s", buff);
strcpy (lastline, buff);
count = 1;
}
}
}
EOF