Self-commentary: MemScribble

Following on from previously.

Idea: All power to the user! As regards to their self-determination on an interactive computing device. Let them scribble over memory, explore memory, and execute machine instructions, interactively!

CPU and memory are what it all boils down to. To get anything done, need a way to set the instruction pointer, and view/edit (scribble) memory.

Basically, I was annoyed that, if I wanted to implement the system described in OROM, I had to endure the sample C code. And to do what? Little more than initialise some data structures, all at once (on human timescales), such that the de-facto way to follow what it’s doing is for me to simulate it in my head as I read the source.

Normally, a C program might involve some hugely complex algorithm that would be tedious to do another way. But here, this was not the case. In a significant number of cases, though not all, the time it takes to write every line of the form struct.field = value; could be better spent just directly entering the values to memory right there in front of you. That’s what the C compiles to in the end; there’s no trace of the English names left, just the memory offsets.

In general, much code is like this. Only a small fraction is dedicated to actual, problem-specific, algorithmic tasks, those with any possibility of being most appropriately expressed as a list of instructions to carry out. Other than this, the vast majority of code that’s written can be divided into at least two groups.

First, there’s the clerical work of file loading, marshalling between different data formats and conventions, often parsing and serialising different dialects of the One True Data Format (text), etc. All of this is so-called “incidental” complexity, not at all part of the problems being solved, but imposed on us by the environment, our representations, and status-quokery. (however, not all “incidental complexity” necessarily lives here.)

The other code is related to the task at hand, but consists entirely of setting up data structures. So if there needs to be, say, a set of gridlines to display, there might be code like this:

for (let i=0; i<nlines; i++) {
    moveto(-extent, 0);
    lineto(extent, 0);

    moveto(0, -extent);
    lineto(0, extent);
}

As I touched on in my graphics notes, graphics programming falls into immediate-mode or retained mode. This code could be either.

  • If the moveto and lineto commands poked pixels directly, or set up state that would be abandoned after the next draw call, then it would be immediate-mode.
  • If they instead inserted geometry into some data structure not destroyed by draw calls, it would be retained-mode.

Two issues I have with code like this. First, since the initialisation code is part of the “code”, it may well be re-initialised and thrown away every time the program runs, even though it basically only needs generating once and then keeping around. It’s only part of the “file”, because there is nowhere else for it to go. It ought to have an independent existence.

Second, and most importantly, the code looks nothing like a grid. Visual concepts should not be stuffed into the language box.

  • When the computer needs to know “how far” when drawing the line, I should not have to think “ah, 22.3 units long.” I should be able to say: THIS far!
  • When it requires a colour, I should not have to think “ah, r-g-b 31, 41, 59” or even go to a colour-picking website. I should be able to say: THIS colour, the one that looks right!
  • When it wants to know how many lines to add: give me a slider, let me adjust until the visual thing I’m crafting looks right.

Relevant: Substroke, Drawing Dynamic Visualizations, the addendum to said talk, dynamic pictures, possibly more by Bret Victor. (who else?)

Statically link in e.g. Win32 LoadLibrary, GetProcAddress and let them do the rest at the user’s request.

This was my answer to the question “OK, suppose you have a visualiser. What is the minimal C program you’d have to write in order to be able to take it from there?”

I was thinking of the work that the compiler would do, since I would rather not. But I realise this may be as simple as looking up a function address in a table and baking that in, which is something I could do, given the appropriate reference material. Still, the compiler would also generate a correctly formatted Portable Executable file, which I don’t really desire to hand-craft anymore.

Anyway, from the perspective of an empty Windows process equipped with a way to execute instructions, if I have access to LoadLibrary I can load DLLs, and by using GetProcAddress I can access whatever Windows-specific APIs I might need, along with some general utilities.

Problem is, malicious code can call LoadLibrary, obtain file write functions, and clobber all files. Need to restrict this somehow.

Precaution: in OS, modify process such that it has permissions to read/write only a specific directory tree. Maybe by running program only as specific user.

Better: encapsulate free-for-all process within a secure sandbox-er.

Perhaps not use native OS functions after all. But this limits exploration 😦

Instead, use protected access to resources. OS deals with CPU-hogging process; firewall restricts network access. But restrict file access to specific dir. And ideally restrict graphics output to single window, and add option in manager to mute audio.

Yeah, here I went off on a security / isolation tangent that I don’t really care about at the moment.

(famous last words? Remember, we can all easily write a shell script that deletes files, or an app that blasts white noise into the sound card. Is what I’m doing any worse than what is already feasible?)

Still, this could prove useful later in the project, depending on how successful it is.

As for reading and modifying processes, what tools do I have?

Probably use Win32 Debug API, or Unix ptrace.

Unfortunate as it is, I’m pretty sure we have to start here…

Core capabilities of supervisor:

  • Navigate memory, view as text and numbers in different layouts
  • Write text, poke to memory
  • Write numerical values, poke to memory
  • Assemble ASM code (plus some higher-level lang), poke to memory
  • Load and save mem regions to/from disk (persistence)

All of the memory and code for this is duplicated in the sandboxed process, where it can be scribbled on if desired

Here I was trying to enumerate the “essential complexity” of the basic capabilities of the environment I want. What is the actual work it needs to be able to do, for me to take it from there in-system? For instance, if I can’t execute code in some way, then all I have is a glorified hex-editor.

The question of executing code is interesting.

  • To really start from the ground-up, there needs to be a way to (a) write machine code to memory and (b) tell the CPU to execute from there.
  • When I start considering this from the context of a self-reflective, self-modifying system: does this not require writable code sections?
  • Will antivirus software complain? Will the OS tell me “right, that’s it! You don’t know what you’re doing; I’m assuming control!”
  • But surely this is an issue any time you want, say, JIT compilation. What is the standard way to handle this?

But I think I see a suspiciously trivial way to circumvent this: define a bytecode!

  • The CPU can only execute memory marked “executable”. My environment might give me funny looks if I also mark it “writeable” and make changes to it all the time.
  • Solution: just push the CPU back one step further! Have the processor execute a constant, static, unchanging “interpreter” that simply reads “instructions” from non-executable, and hence writable, memory.

Supervisor is a fallback

Hmm. What could this have meant? I was often flitting back and forth between the idea of “do absolutely everything in-system from scratch”, and “use each version to create and edit the one that will supersede it, but keep them separate”.

The thing is, you need a starting point, and it will have to have been written in an existing language. One idea is to build in a minimal generator for a self-sustaining system, like in OROM, and use that.

My alternative was to begin with a simple MemScribble hacked together in C++ or something. Then, to use this to build a new, more flexible version of itself, say by implementing the Id object model, which can then be deployed separately.

Ah. That was it. The whole reason I was considering “separation” between a “object” and “meta” or “supervisor” system, is because here we’re dealing with CPU and memory. See my prior comments about being able to “lock up the entire system”. Grrrrr, hopefully I can eventually show what I mean here, instead of having to butcher it with my poor explanatory abilities.

OK. Point of bootstrapping is to bootstrap. lol
Initially, have a REPL for e.g. Coke? 😛

Coke has

(char@ address [index])
(short@ address [index])
(int@ address [index])
(long@ address [index])

“According to locally-defined sizes”

I think this is silly. I’ll have either

(byte@ address [index])
(word@ address [index])
(dword@ address [index])
(qword@ address [index])

(win32-ish convention) or plain old

(int8@ address [index])
(int16@ address [index])
(int32@ address [index])
(int64@ address [index])

eg (set-dword@ 0x00400000 5 0x0BADF00D) does
mem[0x00400000 + 5*4] <- 0x0BADF00D

I was considering using Coke as the non-machine-level language to aim for within MemScribble. I found it funny that it preserved the implementation-defined size of types from C. When doing machine-level work, you think in how-many-bytes, not “is this an int or is it a long int?”

The REPL idea was another possible starting point. Could I hack together a quick-and-dirty Coke REPL and proceed from there? Etc.

I was very confused about where exactly I should start. Starting with less means you can do more, but in a sensible way. Starting with more means you don’t have to do as much to get a “working” system, but you will still have to replace your substrate anyway. And starting with too much, defeats the whole point of bootstrapping from a small generator.

I am still not clear on this. However, I can see a path before me, that should let me try the things I want to do, which is essential.

I will finish by re-visiting my motivation. The whole point of going low-level was because that is how the OROM system is specified. It looks nice and simple, and “efficient” too, dare I say…!

But, back in summer, I really did not enjoy going through the C trying to understand and copy it. In order to help me really grasp, and remember, the steps, I started drawing diagrams on paper.

20171024_215023
Ignore the LISP / Coke bit. It’s confusing and I changed my mind about offset conventions half-way through. But other than that, well: pictures paint a thousand words…

This brought to light the fact that the diagrams were pretty much the representation in which the Id object system was crying out to be implemented!

Think about it: there are diagrams in the original paper. The code samples, and English, constantly refer to “words” of memory, i.e. fixed-size blocks. Stuff has sizes, layouts, and positions in memory. Two values cannot occupy the same location at once. These are spatial concepts! Memory is a 1D space!!

Memory is a discrete 1D space that is convenient to visualise wrapping into 2D, just like text. Just look at any hex editor. I have some interesting things to say about this, but they will have to wait.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.