GP2X Dangerous Linux Hacking Code


Squidge

Certified Guru
Joined
Nov 16, 2003
Messages
8,493
Location
UK
Website
Visit site
The following is my MMU hacking code for changing the cachable, bufferable bits and everything else about the MMU page tables.

Feel free to use in your own code, but you should give credit where its due, and if used in something commercial, I should get a free copy :)

Code:
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <unistd.h>
#include <stropts.h> 
#include <string.h>

extern int errno;

int memfd;

void *trymmap (void *start, size_t length, int prot, int flags, int fd, off_t offset)
{
	char *p;
	int aa;

	//printf ("mmap(%X, %X, %X, %X, %X, %X) ... ", (unsigned int)start, length, prot, flags, fd, (unsigned int)offset);
	p = mmap (start, length, prot, flags, fd, offset);
	if (p == (char *)0xFFFFFFFF)
	{
		aa = errno;
		printf ("failed mmap(%X, %X, %X, %X, %X, %X) errno = %d\n", (unsigned int)start, length, prot, flags, fd, (unsigned int)offset, aa);
	}
	else
	{
		//printf ("OK! (%X)\n", (unsigned int)p);
	}

	return p;
}
 
unsigned char initphys (void)
{
	memfd = open("/dev/mem", O_RDWR);
	if (memfd == -1)
	{
		printf ("Open failed\n");
		return 0;
	}

	printf ("/dev/mem opened successfully - fd = %d\n", memfd);

	return 1;
}

void closephys (void)
{
	close (memfd);
}

int myuname(char *buffer)
{
	asm volatile ("swi #0x90007a");
}

void DecodeCoarse (unsigned int indx, unsigned int sa)
{
	unsigned int cpt[256];
	unsigned int dom = (sa >> 5) & 15;
	unsigned int temp;
	unsigned int i = 0;
	unsigned int wb = 0;
	
	sa &= 0xfffffc00;
	indx *= 1048576;
	
	//printf ("  > %08X\n", sa);
	//printf ("%d\n", 
	lseek (memfd, sa, SEEK_SET);
	memset (cpt, 0, 256*4);
	temp = read (memfd, cpt, 256*4);
	//printf ("%d\n", temp);
	if (temp != 256*4)
	{
		printf ("  # Bad read\n");
		return;
	}

	//printf ("%08X %08X %08X %08X\n", cpt[0], cpt[4], cpt[8], cpt[12]);
	
	for (i = 0; i < 256; i ++)
	{
		if (cpt[i])
		{
			switch (cpt[i] & 3)
			{
				case 0:
					//printf ("  -- [%08X] Invalid --\n", cpt[i]);
					break;
				case 1:
					printf ("  VA: %08X PA: %08X - %08X A: %d %d %d %d D: %d C: %d B: %d\n", indx,
							cpt[i] & 0xFFFF0000, (cpt[i] & 0xFFFF0000) | 0xFFFF,
							(cpt[i] >> 10) & 3, (cpt[i] >> 8) & 3, (cpt[i] >> 6) & 3, 
							(cpt[i] >> 4) & 3, dom, (cpt[i] >> 3) & 1, (cpt[i] >> 2) & 1); 
					break;
				case 2:
					printf ("  VA: %08X PA: %08X - %08X A: %d %d %d %d D: %d C: %d B: %d\n", indx,
							cpt[i] & 0xfffff000, (cpt[i] & 0xfffff000) | 0xFFF,
							(cpt[i] >> 10) & 3, (cpt[i] >> 8) & 3, (cpt[i] >> 6) & 3, 
							(cpt[i] >> 4) & 3, dom, (cpt[i] >> 3) & 1, (cpt[i] >> 2) & 1); 
					// This is where we look for any virtual addresses that map to physical address 0x03000000 and
					// alter the cachable and bufferable bits...
					if (((cpt[i] & 0xffff0000) == 0x03000000) && ((cpt[i] & 12)==0))
					{
						//printf("c and b bits not set, overwriting\n");
						cpt[i] |= 0xFFC;
						wb = 1;
					}
					break;
				case 3:
					//printf ("  -- [%08X/%d] Unsupported --\n", cpt[i],cpt[i] & 3);
					break;
				default:
					//printf ("  -- [%08X/%d] Unknown --\n", cpt[i], cpt[i] & 3);
					break;
			}
		}
		indx += 4096;
	}
	//printf ("%08X %08X %08X %08X\n", cpt[0], cpt[4], cpt[8], cpt[12]);
	if (wb) 
	{
		//printf("Hacking cpt\n");
		lseek (memfd, sa, SEEK_SET);
		temp = write (memfd, cpt, 256*4);
		printf("%d bytes written, %s\n", temp, temp == 1024 ? "yay!" : "oh fooble :(!");
	}
}

void dumppgtable (unsigned int ttb)
{
	unsigned int pgtable[4096];
	char *desctypes[] = {"Invalid", "Coarse", "Section", "Fine"}; 

	memset (pgtable, 0, 4096*4);
	lseek (memfd, ttb, SEEK_SET);
	read (memfd, pgtable, 4096*4);

	int i;
	for (i = 0; i < 4096; i ++)
	{
		int temp;
		
		if (pgtable[i])
		{
			printf ("Indx: %d VA: %08X Type: %d [%s] \n", i, i * 1048576, pgtable[i] & 3, desctypes[pgtable[i]&3]);
			switch (pgtable[i]&3)
			{
				case 0:
					//printf (" -- Invalid --\n");
					break;
				case 1:
					DecodeCoarse(i, pgtable[i]);
					break;
				case 2:
					temp = pgtable[i] & 0xFFF00000;
					//printf ("  PA: %08X - %08X A: %d D: %d C: %d B: %d\n", temp, temp | 0xFFFFF, 
					//		(pgtable[i] >> 10) & 3, (pgtable[i] >> 5) & 15, (pgtable[i] >> 3) & 1, 
					//		(pgtable[i] >> 2) & 1);
							
					break;
				case 3:
					printf ("  -- Unsupported! --\n");
					break;
			}
		}
	}
}

void benchmark (void *memptr)
{
	int starttime = time (NULL);
	int a,b,c,d;
	volatile unsigned int *pp = (unsigned int *) memptr;

	while (starttime == time (NULL));

	printf ("\n\nmemory benchmark of volatile VA: %08X\n\nread test\n", memptr);
	for (d = 0; d < 3; d ++) 
	{
		starttime = time (NULL);
		b = 0;
		c = 0;
		while (starttime == time (NULL))
		{
			for (a = 0; a < 20000; a ++) 
			{
				b += pp[a];
			}
			c ++;
		}
		printf ("Count is %d. %dMB/sec\n",  c, (c * 20000)/1024/1024);
	}

	printf ("write test\n");
	for (d = 0; d < 3; d ++) 
	{
		starttime = time (NULL);
		b = 0;
		c = 0;
		while (starttime == time (NULL))
		{
			for (a = 0; a < 20000; a ++) 
			{
				pp[a] = 0x37014206;
			}
			c ++;
		}
		printf ("Count is %d. %dMB/sec\n",  c, (c * 20000)/1024/1024);
	}

	printf  ("combined test (read, write back)\n");
	for (d = 0; d < 3; d ++) 
	{
		starttime = time (NULL);
		b = 0;
		c = 0;
		while (starttime == time (NULL))
		{
			for (a = 0; a < 20000; a ++) 
			{
				pp[a] += 0x55017601;
			}
			c ++;
		}
		printf ("Count is %d. %dMB/sec\n",  c, (c * 20000)/1024/1024);
	}

	printf ("test complete\n");
}

void hackpgtable (void)
{
	unsigned int oldc1, oldc2, oldc3, oldc4;
	unsigned int newc1 = 0xee120f10, newc2 = 0xe12fff1e;
	unsigned int ttb, ttx;
	char name[256];

	// We need to execute a "MRC p15, 0, r0, c2, c0, 0", to get the pointer to the translation table base, but we can't 
	// do this in user mode, so we have to patch the kernel to get it to run it for us in supervisor mode. We do this 
	// at the moment by overwriting the sys_newuname function and then calling it.

	lseek (memfd, 0x6ec00, SEEK_SET); // fixme: We should ask the kernel for this address rather than assuming it... 
	read (memfd, &oldc1, 4);
	read (memfd, &oldc2, 4);
	read (memfd, &oldc3, 4);
	read (memfd, &oldc4, 4);

	printf ("0:%08X %08X\n", oldc1, oldc2);

	lseek (memfd, 0x6ec00, SEEK_SET); 
	write (memfd, &newc1, 4);
	write (memfd, &newc2, 4);	
	
	ttb = myuname(name);
	
	lseek (memfd, 0x6ec00, SEEK_SET); 
	write (memfd, &oldc1, 4);
	write (memfd, &oldc2, 4);	

	printf ("1:%08X\n", ttb);

	//printf ("Restored contents\n");
	
	// We now have the translation table base ! Walk the table looking for our allocation and hack it :)
	dumppgtable(ttb);	

	// Now drain the write buffer and flush the tlb caches. Something else we can't do in user mode...
	unsigned int tlbc1 = 0xe3a00000; // mov	r0, #0
	unsigned int tlbc2 = 0xee070f9a; // mcr	15, 0, r0, cr7, cr10, 4
	unsigned int tlbc3 = 0xee080f17; // mcr	15, 0, r0, cr8, cr7, 0
	unsigned int tlbc4 = 0xe1a0f00e; // mov	pc, lr

	lseek (memfd, 0x6ec00, SEEK_SET); 
	write (memfd, &tlbc1, 4);
	write (memfd, &tlbc2, 4);	
	write (memfd, &tlbc3, 4);	
	write (memfd, &tlbc4, 4);	
	
	ttx = myuname(name);
	
	//printf ("Return from uname: %08X\n", ttx);
	
	lseek (memfd, 0x6ec00, SEEK_SET); 
	write (memfd, &oldc1, 4);
	write (memfd, &oldc2, 4);	
	write (memfd, &oldc3, 4);	
	write (memfd, &oldc4, 4);	

	//printf ("Restored contents\n");

	//printf ("Pagetable after modification!\n");
	//printf ("-------------------------------\n");
	//dumppgtable(ttb);
}

int
main( int argc, char* argv[] )
{	
	if (!initphys())
		return 0;

	volatile unsigned int *myBuf = trymmap((void *)0, 65536, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0x03000000);
	volatile unsigned int *secbuf = (unsigned int *)malloc (204800);

	//memset ((void *)myBuf, 0x55, 65536);
	//memset ((void *)secbuf, 0x55, 65536);

	printf("mmaped 0x03000000 buffer @ VA: %08X malloc'd buffer @ VA: %08X\n", myBuf, secbuf);

	hackpgtable();

	//benchmark ((void*)myBuf);
	//benchmark ((void*)secbuf);

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
	printf ("\n\nCleaning up...\n");	
	printf ("Closing files...\n");
	close (memfd);
	printf ("Exiting...\n");
	
	return 0;		
}

If you require documentation and/or support, then please don't hesitate to fill up my paypal balance :D
 

Tobriand

Very Active Member
Joined
Dec 27, 2002
Messages
4,071
Age
37
Location
Croydon (UK)
Website
Visit site
I think, if I'm right, it makes Linux aware that the machine actually has 64mb of ram and that it can fiddle with it. Presumably breaking video playback and default 940t operations in the process, given that that's currently how people seem to be able to talk to the second processor. Not from a programmers point of view, incidentally, so if you're looking to use this do not go on my assumptions, but that's what it sounds like in terms of changing which bits are cacheable.

In any event, it's very cool, from the sound of things, and, if it does what I think it might be for, eliminates (potentially) one more of DaveC's (absolutely warrented, but sometimes a little overstated) complaints about the system :)
 

Dzz

stmia r0!, {r2-r9}
Joined
Jan 30, 2006
Messages
1,098
Website
Visit site
That's a pretty fun hack, nice job! I wouldn't have thought of overwriting the code of a system call like that; it could have other uses too! For the system call you chose, do you know how much room is available?

The 920 could share memory with the 940 just fine before this, but that memory would always be uncached on the 920, making access to it slow. With this hack that memory can become cached which should be helpful for complex programs looking to share memory areas heavily accessed by both processors.
 

Dzz

stmia r0!, {r2-r9}
Joined
Jan 30, 2006
Messages
1,098
Website
Visit site
So, what does this do? Make both processors equally usable with all the RAM?
No, it has nothing directly to do with the second processor. It allows the first processor to make some changes to the way it accesses memory. That memory could be shared with the second processor. Using this technique a program that needs a LOT of fast memory (more than the 25ish megabytes normally available) could get fast access to the upper 32mb which would normally be slow.
 
Last edited by a moderator:

BradN

Member
Joined
Jun 22, 2003
Messages
577
Squidge, have you tested if this actually breaks the video player, or actually speeds it up? Or is it not applicable because the video player ends up resetting the settings anyway?
 

Squidge

Certified Guru
Joined
Nov 16, 2003
Messages
8,493
Location
UK
Website
Visit site
Ok, some infos:

Each application runs in it's own memory space with it's own mmu tables, so this code must be included with any program that wants to modify it's mmu tables (for eg. fast access to memory >32mb). Programs running without this hack included will run as normal.

No, it doesn't break the video player unless you happen to overwrite memory that is reserved by the second processor (ie. exactly the same as before), as the changes to the mmu tables only apply to the currently running program. Any program not including this code will run as normal, with slow access to >32mb, even if another program using this hack is running in the background.

The kernel code that is overwritten (sys_newuname) isn't very big in size (it's basically just copies a string into the user provided buffer and returns), so if you want to run a lot of code as supervisor, it would be best if you used kmalloc to allocate kernel memory; copied your code there and branched to that. Only problem is that kmalloc is only available to the kernel, so you need to use a similar technique to what is used here to use that function. You could branch to a function in user land instead, but don't forget that if linux does a task switch on you whilst your in that code, and another task calls sys_newuname, expect a nice big kernel panic. One way of stopping this is disabling interrupts in your new code, but that will screw up timers/etc if you take too long.

The reason I chose sys_newuname for overwriting is that it seems to be the least used system call on Linux - there's little chance of a task switch happening and another function calling that routine, as most programs simply don't care about the os they are running on.
 

MadDog

Member
Joined
Mar 4, 2006
Messages
262
Age
53
Location
UK
Website
www.maddoggames.com
Am I correct in saying this has no effect to the amount of memory avalible on the heap?
All it does is change the cache mode to the upper 32megs?
So to use this mem in an emulator your still need to manage it your self, but at least its at full speed now? :)

Very cleaver mate!

But is it not a bug in gp2x Linux? Should not the kernal code should be protected as its memory is?
 

Squidge

Certified Guru
Joined
Nov 16, 2003
Messages
8,493
Location
UK
Website
Visit site
Am I correct in saying this has no effect to the amount of memory avalible on the heap?

Yes.

All it does is change the cache mode to the upper 32megs?

Yes.

So to use this mem in an emulator your still need to manage it your self, but at least its at full speed now? :)

Yes :)

Very cleaver mate!

But is it not a bug in gp2x Linux? Should not the kernal code should be protected as its memory is?

No, The kernel code is protected as per usual. I use /dev/mem to access the physical memory, and this only works if the code is running as the superuser (root), which is the default on the gp2x. You can't mmap the kernel code either, which is why I use lseek, write, etc instead.

Also, for completeness, the address of sys_newuname was found by looking through /proc/ksym, and there are kernel functions available if you wish to do this automatically - I'm just idle :)
 
Last edited by a moderator:

MadDog

Member
Joined
Mar 4, 2006
Messages
262
Age
53
Location
UK
Website
www.maddoggames.com
No, The kernel code is protected as per usual. I use /dev/mem to access the physical memory, and this only works if the code is running as the superuser (root), which is the default on the gp2x. You can't mmap the kernel code either, which is why I use lseek, write, etc instead.

Also, for completeness, the address of sys_newuname was found by looking through /proc/ksym, and there are kernel functions available if you wish to do this automatically - I'm just idle :)

Ah, I think I get it, still a bit new at this Linux lark. Sorry, a bit offtopic, but if the gp2x was using Linux in the normal way I would be logged in as a normal user and this 'hack' would fail?

(Guess this is why Windows has problems as most login as administrator letting naughty progs to install stuff as they see fit.)
 
Last edited by a moderator:

Squidge

Certified Guru
Joined
Nov 16, 2003
Messages
8,493
Location
UK
Website
Visit site
Yup, if you create a non-superuser, login with that, and then run this hack, it'll fail to open /dev/mem and bail out.
 

Franxis

MAME 4 ALL
Joined
Aug 22, 2004
Messages
788
Age
47
Location
Spain
Website
franxis.zxq.net
I have changed this line in your code to make it to run:
Code:
if (((cpt[i] & 0xff000000) == 0x03000000) && ((cpt[i] & 12)==0))

The interesting part of the terminal output is here:
Code:
0:E1A0C00D E92DD810
1:0062D3BC
  VA: CAFD7000 PA: 03070000 - 03070FFF A: 0 0 0 0 D: 7 C: 0 B: 0
1024 bytes written, yay!

I have used the hardware blitter in this way:
Code:
static void update_display (void)
{
	gp2x_2dregs[0x4 >> 2] = gp2x_physvram[gp2x_nflip]+gp2x_xscreen+(320*gp2x_yscreen); /* Destination */
	gp2x_2dregs[0x0] = (0<<5); /* Destination is 8 bpp */
	gp2x_2dregs[0x8 >> 2] = 320; /* Destination stride size in bytes */
		gp2x_2dregs[0xC >> 2] = (1 << 8) | (1 << 7) | (0 << 5); /* if this works/doesnt, try with bit 7 clear. */
	gp2x_2dregs[0x10 >> 2] = gp2x_video_buffer_physical + (scrbitmap->line[gp2x_yoffset]+gp2x_xoffset)-((unsigned char *)scrbitmap->_private); /* Source address */
	gp2x_2dregs[0x14 >> 2] = scrbitmap->line[1] - scrbitmap->line[0]; /* Source stride */
	gp2x_2dregs[0x2c >> 2] = ((gp2x_height) << 16) | (gp2x_width); /* Height and width to blit */
	gp2x_2dregs[0x30 >> 2] = (1<<11) | (1<<9) | (1<<8 ) | 0xcc; /* Fill from top left to bottom right */
	__asm__ __volatile__ ("" : : :"memory");
	gp2x_2dregs[0x34 >> 2] = 1; 
}

Everything is working fine, but... i have compared at 200 MHz the old MAME GP2X 2.3 (without cached upper memory and without hardware blitter) and this test (with both cached upper memory and hardware blitter) and unfortunately i don't see speed improvements :( :( :( .
 

Franxis

MAME 4 ALL
Joined
Aug 22, 2004
Messages
788
Age
47
Location
Spain
Website
franxis.zxq.net
OK!!!!!. I have it working now :lol:

1) I opened "/dev/mem" twice in MAME (one with minimal lib and one with the MMU hack functions). I have solved it.

2) I have done new tests with this modification in the code:
Code:
					if (((cpt[i] & 0xff000000) == 0x03000000) && ((cpt[i] & 12)==0))
					{
						cpt[i] |= 0xFFC;
						wb = 1;
					}
					else if (((cpt[i] & 0xff000000) == 0x02000000) && ((cpt[i] & 12)==0))
					{
						cpt[i] |= 0xFFC;
						wb = 1;
					}

The memory areas i had in my tests were in 0x3000000, but they were listed as 0x2000000 in MMU caching tables (?). I have run Metal Slug in MAME (it uses a lot of high memory) and i have notice the speed improvement thanks to the MMU caching :D :D :D ...

Regards.
 
Top