Skip to main content
gamedev.net
🔒 Locked

[HELP NEEDED] assembly memset function

Started by RMarin 13 replies 11.7k views
Original Post
RMarin
RMarin


void * x_memset (void* dest, int nByte, size_t count)
{
	__asm {
		mov ecx, [count]
		mov edi, [dest]
		mov eax, [nByte]
		mov ebx, ecx
		shr ecx, 2
		and ebx, 3
		rep stosd
		mov ecx, ebx
		rep stosb
	}
}

When using this stub:

int main ()
{
	char str[] = "almost every programmer should know memset!";
	x_memset (str,'-',6);
	puts (str);

	fflush(stdin);
	getchar();
	return 0;
}
The output with memset() is "------ every programmer should know memset!" the output with my function x_memset() is "-". Any advice?
popsoftheyear
popsoftheyear
Yeah. eax is a 4 byte register, and you only have 1 byte in it. In other words your copying this to dest:

'-' 00 00 00 '-' 00 00 00 '-' 00 00 00 etc.

eax would need to have a copy of '-' in it for every byte. Remove the "shr ecx, 2" and everything after it, and replace with "rep stosb" and you'll see that it works fine. Or after "mov eax, [nByte]", for simplicity's sake add "mov ah, al", "mov ebx, eax", "shl ebx, 16", "or eax, ebx" and you'll see it work correctly.

I'm assuming this is to learn by, but for what it's worth I don't believe rep stos instructions are the optimal solution these days. Most likely neither was my way of getting 4 copies of that byte into eax lol.

Cheers and have fun!
-Scott
Mike nl
Mike nl
Because you pass a char, and load it into eax. So eax will hold 0x0000002D. After the function the memory will be: 0x2D 0x00 0x00 0x00 0x2D 0x2D, or "-" as zero-terminated string.

To avoid, fill every byte of eax with the argument before stosd:
mov al, [nByte]mov ah, almov bx, axshl eax, 16mov ax, bx

Or something like that.

Edit: curse my slow and thoughtful typing!
Million-to-one chances occur nine times out of ten!
RMarin
RMarin
Thanks for the help, I'll try this out. Yeah I'm a college student moving into assembly next semester, I just want the one up on the rest of the class when I move into it. Also, what would you recommend for an efficient replacement of rep stos?
popsoftheyear
popsoftheyear
SSE2 instructions for one. Anyway I think what you're doing now is just fine for learning assembly. If it were for some other purpose most would probably say just go with memset :)

Cheers
-Scott
RMarin
RMarin
Thanks for you help. :)

void * x_memset (void* dest, int nByte, size_t count){	__asm {		mov ecx, [count]		mov edi, [dest]		mov eax, [nByte]		mov ebx, ecx		rep stosb	}}

it works perfect.

Anyone have any suggestions on how to optimize this code? Hopefully with an explanation.
rip-off
rip-off
You could apply this optimisation: [grin]
int main (){	char str[] = "------ every programmer should know memset!";        // no longer required!	// x_memset (str,'-',6);	puts (str);	fflush(stdin);	getchar();	return 0;}

Seriously though, sometimes the high level optimisations will blow any bit twiddling out of the water in terms of gains. Sometimes instead of thinking "how can I memset as fast as possible", you go up one: "how can I reduce the number of calls to memset, or eliminate them". It isn't always possible, but it should be considered.
RMarin
RMarin
Quote:
Original post by rip-off
You could apply this optimisation: [grin]
*** Source Snippet Removed ***
Seriously though, sometimes the high level optimisations will blow any bit twiddling out of the water in terms of gains. Sometimes instead of thinking "how can I memset as fast as possible", you go up one: "how can I reduce the number of calls to memset, or eliminate them". It isn't always possible, but it should be considered.


I totally understand that in my example memset is utterly bloatware status, useless. However, in game engines there are some optimizations for rendering speed, especially when you're dealing with close to 100k objects within one tick of the engine. Knowing inline assembly for functions like memset and memcpy help also with engine security against cheaters, and other exploiters. I'll be using x_memset within my binary to cap off all the jmp routines that my functions create. In order to help prevent (not stop, because that's basically impossible) hooking and other malicious code injection. The reason I want to write my own memset function in inline assembly, is when I call memset I don't want the entire system to know.. at least as easily as DetourFunction((PBYTE*)memset), as to when and what address that I'm calling it at. Less 'call's less security risks.
janta
janta
Usually compiler vendors provide programmers with an optimized general purpose memset/memcpy implementation. Beating that is possible under certain circumstances:
- Having access to hardware specific features (DMA trasfers...)
- Meeting specific size and alignments requirements for these features
- Taking advantage of particular situations (for example, when you know the data you're copying won't be re-read -- at least not immediately -- from its target location
Antheus
Antheus
Quote:
Original post by RMarin at least as easily as DetourFunction((PBYTE*)memset), as to when and what address that I'm calling it at. Less 'call's less security risks.


Last I checked the output, MVC inlines memcpy calls efficiently.

As for security - you don't gain anything by it. At simplest level, copy paste works for assembly.
losethos
losethos
I tried optimizing my MemSet with dword STOSD or qword STOSQ instead of REP STOSB and it didn't help. Modern processors render many optimizations pointless because they pick-up the slack.
Triton
Triton
This is not related to your question, just to warn you that
fflush(stdin)
is undefined behavior in C/C++.

Quoting the standard:

Quote:
int fflush(FILE *ostream);

ostream points to an output stream or an update stream in which the
most recent operation was not input, the fflush function causes any
unwritten data for that stream to be delivered to the host environment to
be written to the file; otherwise, the behavior is undefined.
RMarin
RMarin
Quote:
Original post by Triton
This is not related to your question, just to warn you that
fflush(stdin)
is undefined behavior in C/C++.

Quoting the standard:

Quote:
int fflush(FILE *ostream);

ostream points to an output stream or an update stream in which the
most recent operation was not input, the fflush function causes any
unwritten data for that stream to be delivered to the host environment to
be written to the file; otherwise, the behavior is undefined.


all it's meant for is when I call getchar() next, It holds the screen so I can see it. Until I press another key. I don't use it in actual programming. Just with test stubs.
Triton
Triton
Quote:
Original post by RMarin
all it's meant for is when I call getchar() next, It holds the screen so I can see it. Until I press another key. I don't use it in actual programming. Just with test stubs.


Still, it's undefined behavior. While it may work today, you have no guarantee that it will work tomorrow. You can replace it with something like:

 while(getchar()!='\n');


or...

scanf("%*[^\n]"); scanf("%*c");


Should work! :)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.