Rafael,
Vou escrever o código do prog1.s que é aonde está o printnum para tentar explicar:
.data
S2: .int 10, 20, 30, 40
.text
.globl main
main: pushl %ebx /* salva ebx */
movl $0, %ebx /* ebx = 0; */
movl $S2, %ecx /* ecx = &S2; */
L1: cmpl $4, %ebx /* if (ebx == 4) ? */
je L2 /* goto L2 */
movl (%ecx), %eax /* eax = *ecx; */
call printnum /* imprime valor de eax */
addl $1, %ebx /* ebx += 1; */
addl $4, %ecx /* ecx += 4; */
jmp L1 /* goto L1; */
L2: movl $0, %eax /* eax = 0; (valor de retorno) */
popl %ebx /* restaura ebx */
ret /* retorna */
/* funcao auxiliar para imprimir o valor armazenado em eax. */
S1: .string "%d\n" # string de formato para printf
printnum:
pushl %ecx
pushl %ebx
pushl %eax
pushl $S1
call printf
addl $8, %esp
popl %ebx
popl %ecx
ret
A diferença entre o que o printnum faz e o que o printchar1 fazia é que, enquanto o printchar1 chama a rotina de interrupção para imprimir uma cadeia de caracteres, o printnum chama a função printf do C.
Então, da forma com que o printchar1 foi feito, você passava, antes de chamar o printchar1, como parâmetros, os mesmos parâmetros que ele usa para chamar a interrupção.
Agora, o printnum, da forma como ele foi feito acima, tudo que ele precisa, é que o valor do inteiro que você quer imprimir esteja no EAX antes de você chamar a função. Se você observar a função, ela cuida para empilhar o que é necessário para a chamada do printf do C (repare também que da forma que ela foi feita, só imprime um número de cada vez).
No caso o printchar do prog2.s segue o mesmo conceito do printnum, só que imprimindo 1 char por vez, ao invés de 1 inteiro.