2

I'm writing a tiny OS for my homework and I want to preserve the contents displayed on shell (Because when I use shell to open another application, the screen will be covered by new contents of it, and I want to recover the shell just like before calling the app after return).

I want to accomplish it using C language, but I don't know how to use pointers correctly to handle it. My idea is to read all the data in video memory and save it in a data structure and recover it by rewriting it into video memory, but I tried many ways and it didn't work.

I tried to write function like that:

void storeContents()
{
    uint16_t * ptr =0xB800;
    int cnt = 0;
    while(cnt < 80 * 25)
    {
        store[cnt++] = * (ptr++);
    }
}

I don't know wheather the function is correct, but when I use Bochs to debug, I found when entering this function, it get stucked in this loop and I'm sure there must be something wrong with the pointer(but I don't know what it means, I can't locate which interruption is called)

(0) [0x0000000fe9e6] f000:e9e6 (unk. ctxt): push ax ; 50
<bochs:26> s
Next at t=439640331
(0) [0x0000000fe9e7] f000:e9e7 (unk. ctxt): call .-22398 (0x000f926c) ; e882a8
<bochs:27> s
Next at t=439640332
(0) [0x0000000f926c] f000:926c (unk. ctxt): mov al, 0x20 ; b020
<bochs:28> s
Next at t=439640333
(0) [0x0000000f926e] f000:926e (unk. ctxt): out 0x20, al ; e620
<bochs:29> s
Next at t=439640334
(0) [0x0000000f9270] f000:9270 (unk. ctxt): ret ; c3
<bochs:30> s
Next at t=439640335
(0) [0x0000000fe9ea] f000:e9ea (unk. ctxt): pop ax ; 58
<bochs:31> s
Next at t=439640336
(0) [0x0000000fe9eb] f000:e9eb (unk. ctxt): iret ; cf

Can you help me with it? Thank you! (BTW, I'm using the text mode of graphic card and I don't have file system on my OS yet. The OS is currently under real mode)

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
zjnyly
  • 189
  • 1
  • 11
  • Just have a pointer *point* to the video memory. [The OSDev wiki](https://wiki.osdev.org/Main_Page) have some good examples. – Some programmer dude Apr 08 '21 at 19:02
  • what is preventing you from keeping track of the data that you send to the display? – jsotola Apr 08 '21 at 19:04
  • You now have *two different* questions in your question. One question per question please. Please take the SO [tour], read [ask], as well as [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). – Some programmer dude Apr 09 '21 at 03:15
  • 1
    Are you running code generated from a 32-bit C compiler(gcc) in real mode? Are you using `-m16` with GCC or something similar? – Michael Petch Apr 09 '21 at 07:57
  • Just an observation. Sending 0x20 to port 0x20 by itself indicates this code is sending an EOI (End of interrupt) and the way the code appears it is likely an interrupt from 0x00 to 0x07 (inclusive). The 2 most common interrupts in this range are the timer (0x00) and the keyboard (0x01). The 0xf000 segment is the BIOS, so you look to be debugging an external interrupt handler in the BIOS. – Michael Petch Apr 09 '21 at 16:58
  • The pointer should be `0xB8000`, you need an extra zero. – puppydrum64 Dec 14 '22 at 18:20

1 Answers1

3

On 8086 (and 8088) memory access is done using two registers, a 16-bit segment register and a 16-bit offset value/register. The real address was calculated by taking segment register, shifting it 4 to the left (multiplying it with 16) and adding offset. So if you used DS as segment register and AX as offset register, the real memory address would be (DS * 16) + AX.

The 8086 provided 4 registers to hold the segment value for memory access: DS (Data Segment), SS (Stack Segment), CS (Code Segment) and ES (Extra Segment). Which one would be used depended on op-code. Instruction fetch would always be relative to CS.

Note that segments can overlapp so different segment/offset combos could reference the same memory. For instance [aaaa:0000], [aaa9:0010], [aaa8:0020] and [aaa7:0030] all reference the address 0xaaaa0.

The address bus of the 8086 was 20 bit, so if the address calculation overflowed, it would just wrap around. So [ffff:0010] would be an obfuscated way of writing [0000:0000]. The 80286 added wider address bus and didn't overflow when running in 8086 mode, so for the IBM PC-AT to stay compatible with the original PC, IBM decided to implement some weirdness involving the keyboard controller chip called the A20 gate. (But this is getting WAY off topic.)

So if you would want to access a memory block starting at 0xB8000, you would set DS (or some other segment register) to 0xB800, and accessing your data relative to that using an appropriate offset.

Naturally programming in C you don't have to mess with registers directly, but you still have to understand the segmented memory model of the 8086.

Typically a C compiler for the 8086 lets you compile your code using 3 different addressing modes:

  1. Small memory model - Here all data and code is in the same segment (DS = SS = CS = ES). Pointers are 16 bit and contain the offset in the segment. Size of code and data may only be 64k total.

  2. Large memory model - Similar to "small memory model", but one segment for data and one for code (DS = SS = ES != CS). Pointers are still 16 bits. Size of code and data can be max 64k each.

  3. Huge memory model - Pointers are 32 bit and hold both segment and offset part of a memory address. Pointer arithmetic is done by modifying the offset part of a pointer only. So even if the program can access the whole 1Mb of address space, native data objects in c (including structs and arrays) can only be max 64k, and only when they start at 16-byte alignment.

The video memory on the original PC started at 0xb8000 if IRC. That would be segment 0xb800. To be able to access that we would have to compile using "huge memory model" (32-bit pointers). In text mode, characters are stored as two bytes, one for attributes and one for character code.

Putting all that together, and assuming the video controller has been set to 80x25 alpha numeric mode, we can write to the screen like this:

struct char_cell
{
   unsigned char attribute;
   unsigned char char_code;
};

/*
   How to handle pointers is compiler dependent, but we assume here that 
   the segment value is stored in the high 16 bits. So memory address
   0xb8000 or [b800:0000] can be written as 0xb8000000
 */
#define video_buffer ((struct char_cell *)0xb8000000ul) 

void put_char(int line, int column, int ch)
{
   video_buffer[line * 80 + column].char_code = ch;
}

int get_char(int line, int column, int ch)
{
   return video_buffer[line * 80 + column].char_code;
}

/*
   Saving/restoring buffer doesn't handle saving/restoring cursor position.
   This must be done separately.
 */ 
void save_video_buffer(struct char_cell buffer[80 * 25])
{
   memcpy(save_buffer, video_buffer, sizeof(*buffer) * 80 * 25); 
}

void restore_video_buffer(struct char_cell buffer[80 * 25])
{
   memcpy(video_buffer, save_buffer, sizeof(*buffer) * 80 * 25); 
}

It is long time since I did MS-DOS programming, so the details may be a little wrong.

ecm
  • 2,583
  • 4
  • 21
  • 29
HAL9000
  • 2,138
  • 1
  • 9
  • 20
  • Thank you very much for your patient answer and elegant solution, which gave me a lot of inspiration.However, since memcpy still involves pointer operation, this will cause the same problem I encountered during debug. I think I need to ask this question separately. – zjnyly Apr 09 '21 at 05:58
  • The ideas you have here seem to be applicable to a DOS C compiler(Turbo-C, Watcom C/C++ etc) that had a notion of FAR PTRs. This was tagged with GCC that doesn't have such a notion, although there is an experimental ia16-gcc that is still buggy. CGA/EGA/VGA Text Video memory is at 0xb800:0x0000 – Michael Petch Apr 09 '21 at 16:10
  • 2
    You got the memory models wrong. What you call `small` is actually `tiny`. Your `large` is actually `small`. There is a good [overview of the models on Wikipedia](https://en.wikipedia.org/wiki/Intel_Memory_Model#Memory_models). – ecm Apr 14 '21 at 19:38
  • @ecm, thanks for the correction, MS-DOS was a long time ago, hard to remember every small detail correctly. – HAL9000 May 09 '21 at 15:10