elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Guía rápida para descarga de herramientas gratuitas de seguridad y desinfección


  Mostrar Mensajes
Páginas: 1 2 3 4 5 6 7 [8] 9 10
71  Programación / Programación General / Robusteciendo las Prácticas de Programación con Estándares de Lenguajes y C en: 4 Julio 2012, 14:48 pm
El kernel que he escrito y al que me refiero es el del Nivel 1 en este repositorio:

>> Lista de Núcleos de Entrenamiento (solamente Nivel 1 por ahora) <<


El kernel al que me refiero ha llegado a lo que yo llamo el Nivel 1 de desarrollo.

Eso significa que es el nivel mínimo de usabilidad de un núcleo, que pueda serle útil a un usuario.

El nivel mínimo funcionalmente utilizable (Nivel 1) es poder leer programas del directorio raíz de un floppy mediante sus nombres cortos de archivo, enlazado dinámico básico, funciones de sistema exportadas para los programas, entrada de usuario con un teclado PS/2 y una consola de comandos muy básica.

-------------------------

En lo personal, escribir en ensamblador me es bastante fácil, y sería perfecto si el ensamblador x86 se usara por el resto de plataformas.

Pero ya que ese no es el caso, existen lenguajes como C, que tienen convenciones definidas para codificar sus tipos de datos, y llamadas a rutinas, pasado de parámetros, etc.

Este es una pequeña coyuntura clave: Escribí un programa en C que usa las funciones exportardas del kernel. Al comparar la forma en que se escribe un programa en C, su código generado y la falta de convenciones que usan las funciones de mi kernel actual, me llego a dar cuenta que puedo terminar de robustecer mi kernel si insisto en escribir programas en C y en ensamblador, e incluso reescribir el kernel en C y en ensamblador para comparar el código generado, y empezar a adoptar las convenciones, para obtener una portabilidad e interoperabilidad máximas.

Ya que los estándares de C y las convenciones necesarias son muy voluminosas y técnicas, esto no puede tomarse como una adivinanza, así que voy a tener que reunir esos documentos de especificaciones y traducirlos para estudiarlos a profundidad.

También voy a necesitar aprender a programar en C y en ensamblador para más de una plataforma, para poder ser capaz de crear código portable en todos los niveles. Debería por ejemplo aprender una plataforma ARM además de x86-16, x86-32 y x86-64.

-------------------------

Al usar las funciones del kernel como las tengo actualmente, me doy cuenta de que necesito especificar registros del CPU en el código de C a compilarse por GCC. Esto puede hacerse portable si uso mi lenguaje y compilador Real C, pero en GCC eso es un problema de portabilidad de plataformas enorme.

Este es el primer punto en el que voy a robustecer el kernel: Voy a aprender cómo codificar cualquier tipo de parámetros de funciones y variables de todo tipo.

El código de un programa escrito en C requiere más trabajo y más archivos, algo como esto:


"include/external_exported_functions.inc"
Código:
;Nota: Este archivo es para ser usado por programas externos,
;      para que sepan dónde encontrar las funciones que desean
;      usar, desde la tabla de funciones exportadas del kernel.

;Estos son los ÍNDICES de cada puntero en la tabla de exportes.
;Las convertimos a posiciones de 4 bytes para programas de 32 bits,
;y a posiciones de 8 bytes para programas de 64 bits.
;;

                           clrscr@KernelCore equ 0
           console_kprint_visible@KernelCore equ 1
           console_doCRLFeffect@KernelCore   equ 2
cmdLine__findFirstBlankSpace@KernelCore      equ 3
cmdLine__findFirstNonBlankSpace@KernelCore   equ 4
adjustCommandLineFATname@KernelCore          equ 5
getCommandLineArgsCount@KernelCore           equ 6
copyCommandLineArg@KernelCore                equ 7
cmdLine__charTypeAt@KernelCore               equ 8
cmdLine__skipNonBlankSpaces@KernelCore       equ 9
cmdLine__skipBlankSpaces@KernelCore          equ 10






"exports.asm"
Código:

;Aquí anteponemos un guión bajo a todas las
;funciones de ensamblador a ser llamadas desde C;
;esto es llamado "mangling" o decoración, y es
;una convención de C/el enlazador, para enlazar
;funciones externas al código de C, o entre módulos,
;especialmente entre lenguajes que no comparten las
;convenciones de C, como el caso del ensamblador.
;;

%include "include/external_exported_functions.inc"


global myImportsFunctionsTable
       global _clrscr
       global _console_kprint_visible
       global _console_doCRLFeffect
       global _adjustCommmandLineFATname
       global _getCommandLineArgsCount
       global _copyCommandLineArg
       global _cmdstrbuff


myImportsFunctionsTable:
                   ImportsCount dd 6
                           _clrscr dd clrscr@KernelCore
           _console_kprint_visible dd console_kprint_visible@KernelCore
           _console_doCRLFeffect   dd console_doCRLFeffect@KernelCore
_adjustCommandLineFATname          dd adjustCommandLineFATname@KernelCore
_getCommandLineArgsCount           dd getCommandLineArgsCount@KernelCore
_copyCommandLineArg                dd copyCommandLineArg@KernelCore






_cmdstrbuff times 1024 db 0



"header.asm"
Código:

;Aquí hemos optado por establecer a APPBASE
;simplemente a la ubicación actual en el código,
;representada por el signo de dólar, $.
;
;Ahora no es nuestro código en ensamblador el que
;debe encargarse de establecer la dirección base
;del ejecutable, sino que el enlazador LD, por el
;hecho de que estamos usando varios módulos por
;separado, y en esta complejidad solamente el enlazador
;puede llevar a cabo esto eficientemente.
;;
 ;APPBASE equ 1048576*2
 APPBASE equ $

extern myImportsFunctionsTable

;Nuestro kernel debe leer esta dirección, saltar
;a esta y luego saltarse la cabecera del ejecutable:
;;
 IntendedBaseAddress  dq APPBASE
 NumberOfFieldsBelow  dq 7
 CommandLinePtr       dq 0
 CommandLineLength    dq 0
 CommandLineParamsPtr dq 0
 KernelFnExportsTable dq 0
 KernelVrExportsTable dq 0
 AppFnImportsTable    dq myImportsFunctionsTable
 AppVrImportsTable    dq 0


"start.asm"
Código:
;Si hay código presente, debemos usar
;BITS 32:
;;
 bits 32


;La etiqueta llamada "start"
;debe ser globalmente conocida, para que
;LD o más bien otros módulos de este
;programa pueda encontrarla adecuadamente.
;
;Este es el punto de entrada de nuestra
;aplicación.
;
;"start" es el símbolo que el script LD
;para esta aplicación busca para establecer
;el punto de entrada de la misma, y debe
;declararse como global.
;;
 global start



;Estas son las funciones del kernel, con
;un guión bajo al inicio ("mangled") para
;que C pueda encontrarlas.
;
;Indicamos que son externas porque estos símbolos
;están definidos en "exports.asm":
;;
 extern _clrscr
 extern _console_kprint_visible
 extern _console_doCRLFeffect
 extern _adjustCommmandLineFATname
 extern _getCommandLineArgsCount
 extern _copyCommandLineArg






start:

;Limpiamos la pantalla como siempre:
;;
 call dword[_clrscr]

;Declaramos el símbolo "_main" como
;externo, ya que se encuentra en "main.c",
;y llamamos a main, que está declarado como
;int main(void), al menos de acuerdo a nuestro
;kernel actual y también para esta aplicación
;específica.
;
;Debemos aprender a implementar
;int main(int argc, char **argv)
;por nosotros mismos:
;;
 extern _main
 call _main


;Aquí le devolvemos el control
;al kernel:
;;
 ret



"main.c"
Código:
//Ya que nuestras funciones importadas
//están en una tabla de punteros de funciones,
//debemos declarar la función como un puntero
//a una función, para generar el código adecuado:
///
 extern void (*console_doCRLFeffect)(void);


int main(void)
{
 //Aquí colocaremos una lista de instrucciones
 //para demostrar la sintaxis de AT&T de GAS
 //para ensamblador inline, de todas las
 //combinaciones de opcodes y expresiones que
 //sea útil recordar:
 ///
  __asm__ __volatile__ ("pushl %eax");
  __asm__ __volatile__ ("popl %eax");


 //Llamamos nuestra función proveniente
 //del kernel. Ya que esta no toma parámetros,
 //podemos usarla libremente sin temor a
 //un mal manejo de los parámetros y la pila.
 //Pero esto será un problema para funciones de
 //la librería del kernel que usen parámetros, a
 //menos que estén totalmente de conformidad a
 //las convenciones de C (cdecl):
 ///
  console_doCRLFeffect();

 //Devolvemos 0 desde main, que en las x86
 //lo coloca en AX/EAX/RAX:
 ///
  return 0;
}


"link.ld"
Código:
OUTPUT_FORMAT("binary")
ENTRY(start)
phys = 0x0000000000200000;
SECTIONS
{
  .text phys : AT(phys) {
    code = .;
    *(.text)
    *(.rodata)
    . = ALIGN(0);
  }
  .data : AT(phys + (data - code))
  {
    data = .;
    *(.data)
    . = ALIGN(0);
  }
  .bss : AT(phys + (bss - code))
  {
    bss = .;
    *(.bss)
    . = ALIGN(0);
  }
  end = .;
}


"build.bat"
Código:
nasm -f aout -o header.o header.asm
nasm -f aout -o start.o start.asm
gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -c -o main.o main.c
nasm -f aout -o exports.o exports.asm

ld -T link.ld -o kern0 header.o start.o main.o exports.o


En "build.bat" (o un script Bash de Linux), el orden en el que se compilan los diferentes componentes es arbitrario; pero el orden en que se enlazan con LD es físicamente el mismo que el especificado en la línea de comandos, y debe estar en el orden mostrado aquí (cabecera del programa, inicializador en ensamblador, programa principal, otros archivos de código opcionales, y la tabla de exportes).

link.ld está configurado para generar un archivo binario crudo, y usar una alineación de 0 entre secciones.

Con un script más personalizado, podríamos indicar un ejecutable crudo pero encargarnos nosotros mismos de generar a mano el formato del ejecutable (PE, ELF, AOUT, MZ-EXE, etc.).

Ya que estamos usando el formato AOUT para crear el código objeto de cada pieza del programa mostrado aquí, también deberíamos aprender a manejar dicho formato a bajo nivel, para eventualmente crear herramientas propias y llevar a cabo tareas de depuración y desarrollo más potentes.
72  Programación / Programación General / Programación en Ensamblador (de Java): Hola Mundo en: 3 Julio 2012, 00:37 am
Como nota inicial, si alguien no tiene una mejor idea de cuál sería la mejor forma de que escriba varios posts como respuestas seguidas, incluso quizás después de varias semanas, pido disculpas si envío respuestas a este tema o abro otros nuevos, porque seguramente va a ser mucha la complejidad a manejar, y repartir las diferentes etapas de descubrimientos es mucho más fácil repartiéndolas en diferentes posts, temas y/o respuestas (que serían continuas a menos que alguien responda o consulte algo, o tenga una idea de qué más averiguar o hacer algo interesante y útil con todo esto desde ya, aparte obviamente de realmente aprender Java).

Me tomo la libertad de escribir sobre la programación en ensamblador, pero en el lenguaje dictado por el bytecode de Java.

Las razones más importantes de esto son:

- Para personas demasiado acostumbradas a trabajar con lenguajes al nivel de C o más bajos, trabajar con un ensamblador en un entorno nativo a la orientación a objetos hace mucho más fácil entender a cabalidad cómo funciona realmente la orientación a objetos, en este caso, de Java.

- Dado que Java es una plataforma unificada sin importar el hardware sobre el que corra, estudiarlo a nivel de ensamblador es una oportunidad excepcionalmente buena para practicar la ingeniería inversa y obtener un nivel de experticia por lo menos intermedio.

- Siendo expertos en el ensamblador de Java y en el formato de archivos de bytecode (.class), así como en el uso de las diferentes APIs y teniendo experticia suficiente para escribir un compilador no optimizador como mínimo, se puede dejar de usar el lenguaje Java para producir programas de Java, y eventualmente es posible usar virtualmente cualquier otro lenguaje de programación (incluso ensamblador de Intel x86, e incluso enmascarando las APIs de Java para usarlas como "alias" de otras plataformas como WinAPI o javascript, y así "recortar" un poco lo que se necesita aprender, y unificar un mismo conocimiento en diferentes ambientes de desarrollo). Esto puede llegar a ser especialmente útil, porque significaría que muchos programas escritos lenguajes que tradicinalmente tienen poca o ninguna portabilidad entre plataformas de hardware pueden reutilizarse, convirtiéndolos en instrucciones de bytecode de Java.

- El ensamblador de Java es extremadamente valioso. Los lenguajes de ensamblador "normales" son evitados por la mayoría de programadores no solo por su complejidad, sino que por el hecho de que no están pensados para ser portables. Con Java, los programas generados son más simples que los ejecutables ELF de Linux, y el ensamblador que se escriba en una plataforma, puede correr en cualquier otra en la que haya una JVM. Eso significa que se pueden hacer tareas interesantes de muy bajo nivel, que al mismo tiempo incluyan programación "moderna" orientada a objetos, con la posibilidad de poder seguir corriendo el mismo código en otras plataformas.


Todo esto suena muy interesante, pero para lograrlo, siendo indistintamente buenos programadores de C/ensamblador por ejemplo, o buenos programadores de Java (aunque a un nivel común y corriente, no a nivel de ensamblador y de la arquitectura central), este proceso puede llevar no menos de un año, durante el que hay que releer cosas como el Tutorial de Java, compilar los programas de ejemplo, hacer ingeniería inversa intensa y desensamblarlos (NO decompilarlos, sino que inspeccionar los archivos binarios a mano si es posible y/o hacer un parser), y leer una y otra vez las partes de las especificaciones que se están aplicando en cada ejemplo compilado.

------------------------------------------------------
------------------------------------------------------
------------------------------------------------------
------------------------------------------------------

Pongo un ejemplo simple de un Hola Mundo, como el del Tutorial de Java, pero en Ensamblador de Java.

No he usado un ensamblador nativo de Java "apropiado" sino que NASM, que entre otras cosas no permite usar Big Endian (Java) sino solo Little Endian (x86), así que se ve rápidamente la necesidad de usar un programa ensamblador ya sea de ARM que use Big Endian por defecto, crear un ensamblador propio para Java (en javascript por ejemplo), o buscar un ensamblador "apropiado" nativo a Java.

Esto debe compilarse con lo siguiente, ya sea en Windows, Linux, etc.:

nasm EHLOWorldApp.asm -o EHLOWorldApp.class


Código:
;/*****************************************************************************
;EHLOWorldApp.java
;
; 2012-07-02
;
;
;Demostración inicial de un programa de Java escrito totalmente en ensamblador
;(ensamblador de Java, pero usando NASM para codificar los bytes).
;
;http://devel.cable-modem.org/
;
;Este código es de dominio público (sin derechos de autor).
;Puedes hacer lo que desees con él.
;
;
;*****************************************************************************/


_CLASS_START:

_00000000__magic db 0xCA,0xFE,0xBA,0xBE


_00000004__minor_version dw 0x0000


_00000006__major_version db 0x00,0x33


_00000008__constant_pool_count db 0,29+1

    _0000000A__cp_info_0001:
                           db 0x0A    ;cp_info.tag: CONSTANT_Methodref
                           db 0,0x06  ;cp_info.info.class_index
                           db 0,0x0F  ;cp_info.info.name_and_type_index

    _0000000F__cp_info_00002:
                           db 0x09    ;cp_info.tag: CONSTANT_Fieldref
                           db 0,0x10  ;cp_info.info.class_index
                           db 0,0x11  ;cp_info.info.name_and_type_index

    _00000014__cp_info_0003:
                           db 0x08    ;cp_info.tag: CONSTANT_String
                           db 0,0x12  ;cp_info.info.string_index

    _00000017__cp_info_0004:
                           db 0x0A    ;cp_info.tag: CONSTANT_Methodref
                           db 0,0x13  ;cp_info.info.class_index
                           db 0,0x14  ;cp_info.info.name_and_type_index

    _0000001C__cp_info_0005:
                           db 0x07    ;cp_info.tag: CONSTANT_Class
                           db 0,0x15  ;cp_info.info.name_index

    _0000001F__cp_info_0006:
                           db 0x07    ;cp_info.tag: CONSTANT_Class
                           db 0,0x16  ;cp_info.info.name_index

    _00000022__cp_info_0007:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x06   ;cp_info.info.length
                           db "<init>" ;cp_info.info.bytes[length]

    _0000002B__cp_info_0008:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x03   ;cp_info.info.length
                           db "()V"    ;cp_info.info.bytes[length]

    _00000031__cp_info_0009:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x04   ;cp_info.info.length
                           db "Code"   ;cp_info.info.bytes[length]

    _00000038__cp_info_000A:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x0F   ;cp_info.info.length
                           db "LineNumberTable"   ;cp_info.info.bytes[length]

    _0000004A__cp_info_000B:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x04   ;cp_info.info.length
                           db "main"   ;cp_info.info.bytes[length]

    _00000051__cp_info_000C:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x16   ;cp_info.info.length
                           db "([Ljava/lang/String;)V"   ;cp_info.info.bytes[length]

    _0000006A__cp_info_000D:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x0A   ;cp_info.info.length
                           db "SourceFile"   ;cp_info.info.bytes[length]

    _00000077__cp_info_000E:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x11   ;cp_info.info.length
                           db "EHLOWorldApp.java"   ;cp_info.info.bytes[length]

    _0000008B__cp_info_000F:
                           db 0x0C     ;cp_info.tag: CONSTANT_NameAndType
                           db 0,0x07   ;cp_info.info.name_index
                           db 0,0x08   ;cp_info.info.descriptor_index

    _00000090__cp_info_0010:
                           db 0x07    ;cp_info.tag: CONSTANT_Class
                           db 0,0x17  ;cp_info.info.name_index

    _00000093__cp_info_0011:
                           db 0x0C     ;cp_info.tag: CONSTANT_NameAndType
                           db 0,0x18   ;cp_info.info.name_index
                           db 0,0x19   ;cp_info.info.descriptor_index

    _00000098__cp_info_0012:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x0C   ;cp_info.info.length
                           db "Hello World."   ;cp_info.info.bytes[length]

    _000000A6__cp_info_0013:
                           db 0x07    ;cp_info.tag: CONSTANT_Class
                           db 0,0x1A  ;cp_info.info.name_index

    _000000A9__cp_info_0014:
                           db 0x0C     ;cp_info.tag: CONSTANT_NameAndType
                           db 0,0x1B   ;cp_info.info.name_index
                           db 0,0x1C   ;cp_info.info.descriptor_index

    _000000AE__cp_info_0015:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x0C   ;cp_info.info.length
                           db "EHLOWorldApp"   ;cp_info.info.bytes[length]

    _000000BD__cp_info_0016:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x10   ;cp_info.info.length
                           db "java/lang/Object"   ;cp_info.info.bytes[length]

    _000000D0__cp_info_0017:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x10   ;cp_info.info.length
                           db "java/lang/System"   ;cp_info.info.bytes[length]

    _000000E3__cp_info_0018:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x03   ;cp_info.info.length
                           db "out"   ;cp_info.info.bytes[length]

    _000000E9__cp_info_0019:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x15   ;cp_info.info.length
                           db "Ljava/io/PrintStream;"   ;cp_info.info.bytes[length]

    _00000101__cp_info_001A:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x13   ;cp_info.info.length
                           db "java/io/PrintStream"   ;cp_info.info.bytes[length]

    _00000117__cp_info_001B:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x07   ;cp_info.info.length
                           db "println"   ;cp_info.info.bytes[length]

    _00000121__cp_info_001C:
                           db 0x01     ;cp_info.tag: CONSTANT_Utf8
                           db 0,0x15   ;cp_info.info.length
                           db "(Ljava/lang/String;)V"   ;cp_info.info.bytes[length]

                        __1D:
                           db 0x08    ;cp_info.tag: CONSTANT_String
                           db 0,0x12  ;cp_info.info.string_index




_00000139__access_flags:
                       db 0x00,0x20


_0000013B__this_class:
                       db 0x00,0x05

_0000013D__super_class:
                       db 0x00,0x06

_0000013F__interfaces_count:
                       db 0x00,0x00

;interfaces ------- no interfaces


_00000141__fields_count:
                       db 0x00,0x00


;field_info ------- no fields


_00000143__methods_count:
                       db 0x00,0x02


        _00000145__method_info_0001:
                               db 0x00,0x00     ;access_flags
                               db 0x00,0x07     ;name_index
                               db 0x00,0x08     ;descriptor_index
                               db 0x00,0x01     ;attributes_count
                                   db 0x00,0x09   ;code_attribute/attribute_info[0].attribute_name_index
                                   db 0x00,0x00,0x00,(0x1D)   ;code_attribute/attribute_info[0].attribute_length

                                   db 0X00,0X01       ;code_attribute.max_stack
                                   db 0X00,0X01       ;code_attribute.max_locals
                                   db 0X00,0X00,0X00,0X05   ;code_attribute.code_length

                                      ;INIT: Instructions
                                      ;INIT: Instructions
                                      ;INIT: Instructions
                                      ;INIT: Instructions

                                        db 0X2A         ;aload_0

                                        db 0XB7         ;invokespecial #1
                                        db 0X00
                                        db 0X01

                                        db 0XB1         ;return

                                      ;END: Instructions
                                      ;END: Instructions
                                      ;END: Instructions
                                      ;END: Instructions



                                   db 0X00,0X00    ;code_attribute.exception_table_length


                                   db 0X00,0x01    ;code_attribute.attributes_count       ;1

                                       db 0X00,0X0A             ;LineNumberTable_attribute/attribute_info.attribute_name_index
                                       db 0X00,0X00,0X00,0X06   ;LineNumberTable_attribute/attribute_info.attribute_length
                                           db 0X00,0X01       ;LineNumberTable_attribute.line_number_table_length
                                               db 0X00,0X00       ;line_number_table[0].start_pc
                                               db 0X00,0X05       ;line_number_table[0].line_number


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        _00000170__method_info_0002:
                               db 0x00,0x09     ;access_flags
                               db 0x00,0x0B     ;name_index
                               db 0x00,0x0C     ;descriptor_index
                               db 0x00,0x01     ;attributes_count
                                   db 0x00,0x09   ;code_atribute/attribute_info[0].attribute_name_index
                                   db 0x00,0x00,0x00,(0x25)   ;code_atribute/attribute_info[0].attribute_length
x17E:
                                   db 0x00,0x02      ;code_attribute.max_stack
                                   db 0x00,0x01      ;code_attribute.max_locals
                                   db 0x00,0x00,0x00,(0x09)  ;code_attribute.code_length

                                      ;INIT: Instructions
                                      ;INIT: Instructions
                                      ;INIT: Instructions
                                      ;INIT: Instructions


                                        db 0xB2      ;getstatic #2
                                        db 0x00
                                        db 0x02

                                        db 0x12      ;ldc #3
                                        db 0x1D

                                        db 0xB6      ;invokevirtual #4
                                        db 0x00
                                        db 0x04

                                        db 0xB1      ;return

                                      ;END: Instructions
                                      ;END: Instructions
                                      ;END: Instructions
                                      ;END: Instructions

                                   db 0x00,0x00      ;code_attribute.exception_table_length


                                   db 0x00,0x01      ;code_attribute.attributes_count    ;1

                                       db 0x00,0x0A              ;LineNumberTable_attribute/attrbute_info.attribute_name_index
                                       db 0x00,0x00,0x00,0x0A    ;LineNumberTable_attribute/attribute_info.attribute_length
                                             db 0x00,0x02       ;LineNumberTable_attribute.line_number_table_length
                                                 db 0x00,0x00       ;line_number_table[0].start_pc
                                                 db 0x00,0x07       ;line_number_table[0].line_number

                                                 db 0x00,0x08       ;line_number_table[1].start_pc
                                                 db 0x00,0x08       ;line_number_table[1].line_number



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
f_000001A3__attributes_count db 0x00,0x01     ;attributes_count           ;1
                                db 0x00,0x0D   ;SourceFile_attribute/attribute_info[0].attribute_name_index
                                db 0x00,0x00,0x00,0x02   ;SourceFile_attribute/attribute_info[0].attribute_length


                                db 0x00,0x0E         ;SourceFile_attribute.sourcefile_index



Y el código equivalente en Java normal:

Código:
class EHLOWorldApp
{
 public static void main(String[] args)
 {
  System.out.println("Hello World.");
 }
}



Planeo poco a poco documentar y explicar lo que está pasando, aunque ya que he comentado prácticamente todas las líneas en ensamblador, obviamente entiendo lo que está pasando.

Aunque necesito formalizar las explicaciones, porque voy a necesitarlo cuando trate escribir un ensamblador especial para Java y cuando quiera implementar un compilador de un lenguaje que no sea Java, pero que produzca programas para Java.


------------------------------------------------------
------------------------------------------------------
------------------------------------------------------
------------------------------------------------------




Referencias

Seguir las especificaciones es relativamente fácil para quienes, como yo, están acostumbrados a examinar de forma permanente la estructura de todos los archivos en su computadora, a hacer encajar exactamente cada pieza de un programa en ensamblador (lo que permite encontrar mucho más rápidamente la causa de errores causados por dos o más valores interdependientes) y a pensar en términos de cómo las especificaciones combinan los datos requeridos para una tarea en estructuras de todo tipo (y teniendo en mente qué pudieron haber estado pensando los desarrolladores de dichos estándares para hacer las cosas de la forma en que las hicieron de entre muchos diseños posibles e incluso mucho más simplificados).

Como dije, una de las intenciones principales de todo esto es eventualmente poder escribir programas de Java con lenguajes "normales", como C, Visual Basic, Pascal, etc.

Pero para eso se necesita tener gran experticia en el ensamblador de Java, y en la estructura de bajo nivel del lenguaje, las clases, y todos los demás aspectos de la máquina virtual de Java, para poder crear equivalentes para hacer que de un lenguaje no oficial se pueda producir bytecode apropiado y totalmente correcto.

Esta es la lista de lo que hay que empezar a leer, y no hay que olvidar tampoco las referencias de las diferentes APIs, y también el tutorial de Java, que sería extremadamente interesante "remasterizar" en su versión en ensamblador Java, para comprender cada detalle de lo que contiene y hacer posible que se convierta en un libro de alto nivel capaz de volver a un programador en un experto absoluto en Java, ya sea programando en alto nivel o en el nivel más bajo posible:


Especificaciones de bajo nivel de Java

El capítulo 4 habla específicamente del formato de los archivos .class


Wikipedia: Formato de los archivos .class

Wikipedia: Resumen del bytecode de Java

Wikipedia: Resumen del conjunto de instrucciones de Java


También vale la pena mencionar el comando básico del desensamblador del JDK:

javap -c EHLOWorldApp.class
73  Programación / Programación General / Programación por Diversión: Núcleo Básico (2012-06-21) en: 22 Junio 2012, 05:49 am
Como había mencionado hace un par de semanas, quería implementar un programa de bajo nivel que fuera simple pero a la vez útil, y para eso era necesario que fuera capaz de correr programas externos.

Por ahora, solamente pueden leerse archivos de una disquetera interna, no una USB.

Poco a poco voy a explicar e indexar la información de cómo hace cada cosa, pero a la vista del usuario este programa es como una versión Alpha de una consola de MS-DOS en su primera versión, la primera de todas, así que tiene soporte para:

- Teclado
- Pantalla de texto
- Correr programas de 32 bits desde la disquetera
- El sistema exporta funciones y variables, para que los programas externos no tengan que reimplementar la misma funcionalidad. Poco a poco voy a migrar la funcionalidad de programas externos que demuestren ser valiosos al núcleo mismo del sistema.



Lo bueno de esto es que con la capacidad de correr programas externos, se puede llevar a cabo absolutamente cualquier tarea, por ejemplo, correr un programa binario compilado en C desde Windows/Linux, que active el modo gráfico, que genere algún gráfico, y que desee guardarlo a la disquetera.

O un intérprete increíblemente simple de mi lenguaje Real C, o para entrar en estándar, un intérprete de javascript que comience con funciones como alert, el operador typeof y la declaración de variables, etc...

Con esto se hace fácil estudiar minuciosamente cualquier tarea de programación, a un grado excepcional, y todos los detalles aprendidos, usarlos para optimizar y de hecho comprender la creación de aplicaciones en sistemas ya establecidos, como Windows, Linux, Mac, etc.

Este grupo de temas que pienso poner sí va a ser lo más dinámico y prolífico que probablemente haya hecho hasta ahora en mi vida en la Web, ya que este estudio lo llevo a cabo durante todo el día, todos los días (a menos que necesite descansar), así que pido que me tengan "paciencia".

Pero lo que sí puedo prometer es que esto va a ser interesante y provechoso y poco a poco debería ser capaz de demostrar que esto es en enorme medida, inmediatamente aplicado a cualquier plataforma de computadoras.



---------------------------------------------------------------


Empiezo con el código más básico.

La pregunta es, ¿qué les parece el código fuente que he escrito hasta ahora? ¿Qué podría hacer para que sea más legible? ¿Qué idea tienen para hacer algo útil? Lo próximo que se me ocurre en lo personal es discutir programas de C con complejidad cada vez mayor, vista desde el punto de vista de un compilador, no solo compilarlos, y cómo interactúan con la máquina.

>> Muestra de un Sistema Ejecutivo Básico <<


También, empiezo inmediatamente a ver cuál es la siguiente tarea que debería llevar a cabo en lo poco que me queda de hoy.

74  Programación / Programación C/C++ / Re: Dudaa sobre una variable utilizada en un programa en: 24 Marzo 2012, 12:12 pm
NOTA: No recomiendo usar los dos programas anteriores.

El primero, el que yo hice, tiene código que no hace nada (especialmente el que se encarga de saltarse la comprobación de números pares mayores a 2). Además no tiene las optimizaciones bien implementadas.

El segundo, el que se publicó aquí, no es del todo intuitivo, no es muy modular, y la lógica del código es un poco forzada.



Después de haber pasado un par de horas más estudiando, logré investigar el por qué es útil dividir nuestro número máximo entre 2.

Simplemente es porque los números más allá de la mitad de la secuencia, más allá de (numero_limite/2), NUNCA dividirán igualmente/uniformemente/exactamente, y por lo tanto son inútiles para detectar un primo (esto lo he escrito un poco más ampliamente en los comentarios del programa).

La versión "limpia" y para evaluar solamente 1 número es así:





Código:
int EsUnPrimo( int MiNumero )
{
 int i=2;


 if     (MiNumero < 2)                 return 0;
 else if(MiNumero!=2 && MiNumero%2==0) return 0;
 else if(MiNumero == 2 || MiNumero==3) return 1;


 while( i < MiNumero/2+1 )
 {
  if( MiNumero % i == 0 ) return 0;
  i++;
 }


 return 1;
}



O con comentarios:



Código:
/*****************************************************************************


Versión "limpia", compacta, y un poco optimizada, de una función que determina
si un número individual dado, es primo o no.

http://devel.no-ip.org/

Este código es de dominio público (sin derechos de autor).
Puedes hacer lo que desees con él.


*****************************************************************************/

//Devuelve un valor diferente de 0 (o 1 específicamente aquí) si es primo;
//o devuelve 0 si no es primo:
//
///
 int EsUnPrimo( int MiNumero )
 {
  //Con esto comprobaremos los números, desde 2:
  ///
    int i=2;




  //Números menores a 2 no son primos:
  ///
   if(MiNumero < 2) return 0;
  



  //Después de comprobar el número sea positivo (y mayor o igual a 2)
  //con el IF inmediatamente arriba de esto, ahora esto es para descartar todos
  //los números positivos pares diferentes de 2, ya que sabemos que ningún número
  //par a excepción de 2, es primo. Con esto aceleramos nuestra función.
  ///
   else if(MiNumero!=2 && MiNumero%2==0) return 0;



  //2 y 3 son los únicos números que además de
  //ser primos, son demasiado pequeños para ser
  //tomados en cuenta por la optimización de la
  //división, así que los devolvemos estáticamente
  //como primos aquí:
  ///
   else if(MiNumero == 2 || MiNumero==3) return 1;





  //Solo la mitad de la secuencia de nuestro número es útil
  //para detectar si nuestro número, llamado 'MiNumero',
  //es primo:
  ///
    while( i < MiNumero/2+1 )
    {
     //Si dividimos exactamente por un número diferente
     //de 1 o de 'MiNumero', significa que el número
     //NO ES PRIMO, así que nos detenemos, devolviendo 0,
     //o FALSO:
     ///
      if( MiNumero % i == 0 ) return 0;


     //Si no detectamos nada especial,
     //pasamos al siguiente número de prueba:
     ///
       i++;
    }



   //Si llegamos aquí, significa que el número es primo,
   //así que devolvemos 1, o VERDADERO:
   ///
    return 1;
 }









Aquí pongo el programa que hice ahora, con más documentación y con todas las optimizaciones posibles.



Aunque todavía no estoy conforme, porque debería haber cambiado todas las operaciones de división y de módulo (residuo) por operaciones de desplazamiento de bits, que son mucho más rápidas.

Claro que voy a seguir tratando de aplicar esas optimizaciones, pero no creo que las termine en un tiempo específico como lo que logré hasta ahora, sino más bien de mediano a largo plazo, quizás en semanas o meses, etc.


Pero espero que este programa pueda servir como un ejemplo efectivo y básico para una de las preguntas "principiantes" más comunes (aunque tal vez no tanto, cuando se hace a conciencia), y que sirva como la respuesta óptimamente depurada, optimizada y eficiente, y que al mismo tiempo sea tan fácil de entender como lo permite la programación.


primos3.c
Código:
/*****************************************************************************


Demostración DEPURADA Y UN POCO OPTIMIZADA de cómo encontrar
los primos menores a un número dado.

http://devel.no-ip.org/

Este código es de dominio público (sin derechos de autor).
Puedes hacer lo que desees con él.


*****************************************************************************/

#include <stdio.h>
#include <string.h>



//Declaramos nuestra función personalizada antes de usarla
//en main(). Esta es una buena práctica de programación en C,
//especialmente para que el compilador pueda verificar
//que los tipos de datos usados con los argumentos de la función sean correctos.
///
  void EncontrarNumerosPrimosInferiores(int Limite_A_Inferiorizar);





int main(int argc, char **argv)
{
 int numero_limite=0;



 //'argc' es un argumento que puede estar en main(),
 //e indica cuántas opciones escribimos en la línea de comandos
 //del programa.
 //
 //'argc' siempre es 1 cuando se llama, por ejemplo, simplemente 'primos.exe'.
 //
 //pero si lo llamamos con algo como 'primos.exe 3133', 'argc' será 2 y
 //argv[1] contendrá la cadena "3133", que convertiremos a un entero.
 //
 ///
   if(argc<2)
   {
    printf("---------------------------------------------------\n");
    printf("Por favor use este programa con un numero. Ejemplo:\n");
    printf("     %s 31\n\n", argv[0]);
    printf("       En donde 31 puede ser cualquier otro numero entero\n");
    printf("       igual o mayor a 2.\n");
    printf("---------------------------------------------------\n\n");
    return 0;
   }


 //Convertir nuestra cadena numérica, pasada por la línea de comandos,
 //a un entero:
 ///
  numero_limite=atoi(argv[1]);


 //Si nuestro número límite es mayor o igual a 2, todo está bien.
 //De lo contrario, avisamos que el número especificado es un error:
 ///
  if(numero_limite>=2)
  {
     EncontrarNumerosPrimosInferiores(numero_limite);
  }
   else
   {
    printf("ERROR: El numero primo debe ser un entero mayor o igual a 2.\n\n");
   }


 //Volvemos al sistema:
 ///
  return 0;
}







void EncontrarNumerosPrimosInferiores(int Limite_A_Inferiorizar)
{
  //Aplicamos nuestro truco para encontrar solo los
  //primos menores al número dado:
  ///
   Limite_A_Inferiorizar=Limite_A_Inferiorizar-1;

  //Mantenemos un número actual que comprobar, a partir de 2:
  ///
    int numero_actual=2;


  //Necesitamos un número probador, que probará todos los números entre
  //2 hasta un número antes de nuestro numero_actual (es decir
  //mientras el numero_probador sea menor que numero_actual), y hace esto
  //para verificar que sea primo:
  ///
    int numero_probador=2;


  //Si este número no es 0, significa que el numero_actual no es primo
  //porque la división fue inexacta y dejó un residuo.
  ///
    int residuo_primos=-1;



  //Esto es para saber cuántos primos hay en total, menores
  //al numero_limite:
  ///
   int contador_primos=0;


  //Este es un bucle simple que se limita a buscar todos los
  //números primos directamente dentro del rango de 2 a numero_limite,
  //inclusive:
  ///
   while(numero_actual<=Limite_A_Inferiorizar)
   {
    //No hay ningún número par que sea primo, a excepción
    //de 2, así que esta línea lo que hace es ver si el
    //número actual es par, comparando con el par original
    //(2) para evitar tener que comprobar, y acelerar aún
    //más nuestro programa.
    //
    //Esto es para descartar todos los números pares, ya que
    //sabemos que ningún número par a excepción de 2, es primo.
    ///
     if(numero_actual!=2 && numero_actual%2==0)
     {
      numero_actual++;
      continue;
     }


    //Inicializar el residuo para determinar si el numero_actual
    //es primo. La inicializamos a -1 porque el bucle interno se detiene si
    //residuo_primos es 0; es decir, si no hubo residuo en una
    //operación, lo que significa que un número es exactamente divisible
    //entre otro.
    //
    //Esto funciona apenas porque en el caso de los números primos
    //2 y 3, y al usar la optimización de división a la mitad, estos
    //dos números quedan sin ser abarcados por esa lógica, y serían
    //descartados, si no fuera por la salvedad de que residuo_primos
    //es inicializado constantemente a un valor diferente de 0
    //(en este caso -1, pero podría ser cualquier otro valor).
    //
    //Esa inicialización a un valor diferente de 0 es especialmente para
    //estos dos números, demasiado pequeños para ser tomados en cuenta
    //si dividimos el (numero_actual/2). En otras palabras, los números
    //2 y 3 son tan pequeños que el bucle interno no correrá, así que
    //nuestro programa tiene que estar predeterminadamente configurado
    //a buscar números desde 2, y tener un valor diferente de 0 mientras
    //el numero_actual es 2 y 3, de forma estática.
    //
    //Para todos los demás casos (comenzando con el no primo 4 y el primo 5),
    //residuos_primos se configura a un valor diferente de 0 si el numero_actual es primo;
    //o a 0 si el numero_actual no es primo:
    ///
     residuo_primos=-1;




    //Mientras que el bucle WHILE externo se preocupa de recorrer
    //cada número principal justo hasta numero_limite,
    //este bucle WHILE interno se preocupa de probar cada numero_actual
    //dividiéndolo entre cada numero_probador, que va desde 2 hasta
    //justo antes de numero_actual:
    //
    //
    //Aquí aplicamos la optimización por el hecho de que solo se
    //necesita comprobar con la mitad de la secuencia de números
    //que llevan al número actual (porque ninguno de los números
    //que son mayores a la mitad de la secuencia dividirá igualmente/uniformemente/exactamente
    //al número actual, así que son inútiles para saber si el número
    //actual es primo).
    //
    //Lo que hacemos es dividir, pero para optimizar para el CPU
    //usamos desplazamiento de bits a la derecha en 1 posición
    //(cada bit que desplazamos a la derecha en una variable entera
    //positiva, o sin signo, equivale a dividir entre 2, 4, 8, 16,
    //32, 64, 128 bits, si desplazamos 1, 2, 3, 4, 5, 6, 7 bits,
    //respectivamente --y desplazar al la izquierda equivale a
    //multiplicar por esas "potencias de 2"--). Esto es mucho más
    //rápido que una operación con el operador de división /, o con
    //el operador de módulo %.
    //
    //Luego le sumamos 1. Aunque parece ser opcional, tal vez sea
    //preferible en caso de existir, entre infinitos números, alguno
    //que necesite comprobar un número más, en la primera mitad, cuando esa división daría
    //un resultado fraccionario de 0.5, en caso de ser un impar, y que pudiera
    //dar la posibilidad de que ese número opcional decida si el número es
    //primo o no.
    //
    ///
     while(numero_probador < ((numero_actual>>1)+1))
     {
      //Si encontramos una operación en la que el residuo fuera 0,
      //y ese número naturalmente no será sí mismo, porque estamos
      //comprobando solo menores a numero_actual en este bucle
      //interno (de hecho solo hasta numero_actual/2),
      //significa que este número no es primo:
      ///
       residuo_primos=numero_actual%numero_probador;
       if(residuo_primos==0)
       {
//        printf("Detenido en %d %% %d\n", numero_actual, numero_probador);  /*DEBUG*/
        break;
       }

      //Prepararnos a probar si el numero_actual es primo, pasando ya al siguiente
      //número de prueba:
      ///
       numero_probador++;
     }


      //Si el resudio para el numero_actual no es 0,
      //significa que nuestro numero_actual es primo,
      //DE ACUERDO A LA LÓGICA PARTICULAR DE ESTE PROGRAMA:
      ///
       if(residuo_primos)
       {
        //Mostramos el numero_actual, que es un primo:
        ///
//         printf("numero_actual == %d; residuo_primos == %d\n", numero_actual, residuo_primos);  /*DEBUG*/
         printf("%d\n", numero_actual);

        //Contabilizar que hemos encontrado un primo más:
        ///
         contador_primos++;
       }
//        else                                                        ///////////////////////////////*DEBUG*/
//        {                                                                                         /*DEBUG*/
//         printf("{numero_actual == %d; residuo_primos == %d}\n", numero_actual, residuo_primos);  /*DEBUG*/
//        }                                                           ///////////////////////////////*DEBUG*/
    

    //Volvemos a inicializar nuestro número probador para la
    //siguiente repetición del bucle:
    ///
     numero_probador=2;


    //Avanzamos nuestro numero_actual para comprobar el
    //siguiente posible número primo:
    ///
     numero_actual++;
   }

 printf("Numeros primos menores a %d encontrados: %d", Limite_A_Inferiorizar+1, contador_primos);
}









Este programa puede encontrar los 82025 primos que hay entre 2 y 1048576 en 8 minutos con 40 segundos, en una máquina AMD de 32 bits a 2 GHz y con 1 núcleo, y compilando con DJGPP.

En úna máquina Centrino a 1.6 GHz, y 1 núcleo de 32 bits, se lleva 5 minutos con 22 segundos (quizás la otra máquina estaba un poco cargada de tareas en segundo plano, pero parece que los tiempos son más o menos razonables).


75  Programación / Programación C/C++ / Re: Dudaa sobre una variable utilizada en un programa en: 24 Marzo 2012, 07:35 am
En ese programa, la variable n se usa para ejecutar el printf solo cuando es 0. Además, el número actual, numprimo se está dividiendo entre 0.

Estuve tratando de modificar este programa, y lo comenté lo mejor que pude.

Comprobé que toda esta lógica está concatenada y es un poco más compleja de lo que parece a simple vista. Según lo que recuerdo vagamente, se puede determinar si un número es primo usando solo la mitad de la secuencia de números para acelerar el programa; de ahí vendría la división de numprimo/2.

En particular, me interesa terminar de descifrar el por qué y cómo funciona exactamente el ser capaz de determinar si un número es primo, cuando el residuo de operaciones repetitivas que lo sobreescriben es 0. Traté de implementar el bucle con la división entre 2, pero todavía no puedo lograrlo, y seguiré intentando hasta donde pueda.




Aquí traté de explicar bastantes más trucos, e hice mi propia implementación, bastante eficiente, y más fácil de leer (y ligeramente diferente, pero más intuitiva y modularizado con una función fácilmente reutilizable):

Documento: ¿Cómo encontrar todos los números primos menores a un número dado?

Mi código (con muchos comentarios):
primos1.c

Código:
#include <stdio.h>
#include <string.h>



//Declaramos nuestra función personalizada antes de usarla
//en main(). Esta es una buena práctica de programación en C,
//especialmente para que el compilador pueda verificar
//que los tipos de datos usados con los argumentos de la función sean correctos.
///
void EncontrarNumerosPrimosInferiores(int Limite_A_Inferiorizar);


int main(int argc, char **argv)
{
 int numero_limite=0;



 //'argc' es un argumento que puede estar en main(),
 //e indica cuántas opciones escribimos en la línea de comandos
 //del programa.
 //
 //'argc' siempre es 1 cuando se llama, por ejemplo, simplemente 'primos.exe'.
 //
 //pero si lo llamamos con algo como 'primos.exe 3133', 'argc' será 2 y
 //argv[1] contendrá la cadena "3133", que convertiremos a un entero.
 //
 ///
   if(argc<2)
   {
    printf("---------------------------------------------------\n");
    printf("Por favor use este programa con un numero. Ejemplo:\n");
    printf("     %s 31\n\n", argv[0]);
    printf("       En donde 31 puede ser cualquier otro numero entero\n");
    printf("       igual o mayor a 2.\n");
    printf("---------------------------------------------------\n\n");
    return 0;
   }


 //Convertir nuestra cadena numérica, pasada por la línea de comandos,
 //a un entero:
 ///
  numero_limite=atoi(argv[1]);


 //Si nuestro número límite es mayor o igual a 2, todo está bien.
 //De lo contrario, avisamos que el número especificado es un error:
 ///
  if(numero_limite>=2)
  {
     EncontrarNumerosPrimosInferiores(numero_limite);
  }
   else
   {
    printf("ERROR: El numero primo debe ser un entero mayor o igual a 2.\n\n");
   }


 //Volvemos al sistema:
 ///
  return 0;
}







void EncontrarNumerosPrimosInferiores(int Limite_A_Inferiorizar)
{
  //Aplicamos nuestro truco para encontrar solo los
  //primos menores al número dado:
  ///
   Limite_A_Inferiorizar=Limite_A_Inferiorizar-1;

  //Mantenemos un número actual que comprobar, a partir de 2:
  ///
    int numero_actual=2;


  //Necesitamos un número probador, que probará todos los números entre
  //2 hasta un número antes de nuestro numero_actual (es decir
  //mientras el numero_probador sea menor que numero_actual), y hace esto
  //para verificar que sea primo:
  ///
    int numero_probador=2;


  //Si este número no es 0, significa que el numero_actual no es primo
  //porque la división fue inexacta y dejó un residuo.
  ///
    int residuo_primos=-1;



  //Esto es para saber cuántos primos hay en total, menores
  //al numero_limite:
  ///
   int contador_primos=0;



  //Este es un bucle simple que se limita a buscar todos los
  //números primos directamente dentro del rango de 2 a numero_limite,
  //inclusive:
  ///
   while(numero_actual<=Limite_A_Inferiorizar)
   {
    //No hay ningún número par que sea primo, a excepción
    //de 2, así que esta línea lo que hace es ver si el
    //número actual es par, comparando con los pares originales
    //(2 y 0) para evitar tener que comprobar, y acelerar aún
    //más nuestro programa.
    //
    //Hacemos esto al aplicar una máscara AND a 'numeroprimo'
    //con el valor 3 (11 en binario) para quedarnos con los
    //2 bits de menor peso. Si esto es diferente de 2 o de 0,
    //solo puede ser 1 o 3, pero si es 2 o 0, el 'numeroprimo'
    //actual no puede ser primo.
    //
    //Esto es para descartar todos los números pares, ya que
    //sabemos que ningún número par a excepción de 2, es primo.
    ///
     if(numero_actual!=2 && (numero_actual&3 == 2 || numero_actual&3 == 0))continue;


    //Inicializar el residuo para determinar si el numero_actual
    //es primo. La inicializamos a -1 porque el bucle se detiene si
    //residuo_primos es 0; es decir, si no hubo residuo en una
    //operación, lo que significa que un número es exactamente divisible
    //entre otro:
    ///
     residuo_primos=-1;




    //Mientras que el bucle WHILE externo se preocupa de recorrer
    //cada número principal justo hasta numero_limite,
    //este bucle WHILE interno se preocupa de probar cada numero_actual
    //dividiéndolo entre cada numero_probador, que va desde 2 hasta
    //justo antes de numero_actual:
    ///
     while(numero_probador<numero_actual)
     {
      //Si encontramos una operación en la que el residuo fuera 0,
      //y ese número naturalmente no será sí mismo, porque estamos
      //comprobando solo menores a numero_actual en este bucle
      //interno, significa que este número no es primo:
      ///
      if(!(residuo_primos=numero_actual%numero_probador))
      {
//       printf("Detenido en %d %% %d\n", numero_actual, numero_probador);
       break;
      }

      //Probar si el numero_actual es primo, usando el siguiente
      //número de prueba:
      ///
       numero_probador++;
     }


      //Si el resudio para el numero_actual no es 0,
      //o si el numero_actual es 2, significa que nuestro
      //numero_actual es primo, DE ACUERDO A LA LÓGICA PARTICULAR DE ESTE PROGRAMA:
      ///
       if(residuo_primos || numero_actual==2)
       {
        //Mostramos el numero_actual, que es un primo:
        ///
         printf("%d\n",numero_actual);

        //Contabilizar que hemos encontrado un primo más:
        ///
         contador_primos++;
       }
    

    //Volvemos a inicializar nuestro número probador para la
    //siguiente repetición del bucle:
    ///
     numero_probador=2;


    //Avanzamos nuestro numero_actual para comprobar el
    //siguiente posible número primo:
    ///
     numero_actual++;
   }

 printf("Numeros primos menores a %d encontrados: %d", Limite_A_Inferiorizar+1, contador_primos);
}




Código original (con más comentarios e intenté optimizarlo/modificarlo ligeramente, y documentar eso también):
primos2.c

Código:
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
 //Declaramos 5 variables enteras:
 ///
   int n,          //Para probar que cada número sea primo
       i,          //Para dividir cada número a comprobar con esta valor, de 2 hasta (numprimo/2)
       num,        //El valor introducido por el usuario
       numprimo=2, //El número a comprobar que va desde 2 hasta nuestro máximo
       t=0;        //Contador para mostrar cuántos números primos menores al máximo encontramos


 //Imprimir un mensaje pidiéndole al usuario que
 //escriba el número máximo deseado, para el que
 //debemos encontrar otros primos menores que este
 //(que no tienen matemáticamente nada que ver con el
 //número máximo... el número máximo es solo para decirle
 //al programa hasta antes de qué número, primo o no, buscar
 //primos):
 ///
  printf("Intruduzca un numero: ");


 //Leemos la entrada del usuario como un entero,
 //para lo que usamos la especificación de conversión
 //"%d" (así se le llama técnicamente a ese porciento
 //seguido de una letra). Esto es para que scanf convierta
 //automáticamente el valor, de una cadena de texto, a un
 //número entero.
 //
 //Guardaremos el valor leído en nuestra variable 'num'. Pero
 //ya que scanf necesita conocer la ubicación en memoria de la
 //variable, usamos el operador ampersand para pasarle
 //la dirección de memoria de esa variable en lugar de una copia
 //del valor que contiene.  Así, usamos '&num' (dirección de memoria)
 //en lugar de 'num' (simple copia de un valor numérico).
 //
 //Gracias a esto, scanf es capaz de colocar el valor entero
 //que convirtió a partir de la entrada de usuario (del número
 //máximo) exactamente en donde está nuestra variable 'num'
 //para que podamos usar este valor más adelante.
 ///
  scanf("%d",&num);









 //Un primo es un número entero positivo mayor a 1,
 //que solo puede dividirse exactamente por números
 //positivos enteros que son 1 y sí mismo.
 //Ya que no tomamos a 1 como primo, empezamos por 2
 //en la varible 'numprimo', con 'numprimo = 2'.
 //
 //'numprimo < num' signifca que el FOR se detendrá cuando
 //'numprimo' sea igual o mayor que 'num'.
 //
 // Esta parte también hace que si 'num' es menor a 2, o un
 // número negativo, no se procese nada, ya que esos son
 // números inválidos para buscar primos.
 //
 //Recordemos que 'num' es el valor que el usuario ingresó.
 //
 //
 //'numprimo++' significa que le sumaremos 1 a 'numprimo'
 //en cada repetición del FOR.
 //
 //Este primer bucle FOR, es el bucle externo, que se encarga
 //de imprimir cada número primo menor que el especificado, si
 //se determina que cada dicho número es realmente primo.
 //
 ///
   for (numprimo = 2; numprimo <num; numprimo++)
   {


     //Esta variable es para controlar el bucle interno:
     ///
      n=0;   //Esta variable es temporal para saber si 'i' es primo
      i=2;   //Nuevamente empezamos en 2 por la definición de los primos









       //Bucle interno WHILE:
       //Este bucle prueba cada número en i, desde 2 hasta lo
       //que el bucle FOR exterior tiene configurado como el
       //número a evaluar actualmente, en la variable 'numprimo'
       //
       //El 'numprimo/2' es una optimización matemática que podemos
       //usar con confianza y sin pensar mucho, pero que, hasta
       //donde recuerdo, dice que podemos determinar si un número
       //es primo o no, usando solamente la mitad de los números
       //que el número que estamos evaulando. Es en esos números
       //en los que se definirá si un número es primo o no, y una
       //de las razones es que en este rango encontramos números
       //más fundamentales, como 2, que ponen a prueba a cualquier
       //número.
       //
       //La intención de esto es que nuestro programa sea más rápido,
       //al aprovechar este hecho matemático que nos permite tener
       //que procesar solo la mitad de los números (y hay otras
       //optmizaciones matemáticas considerablemente más complejas).
       //
       //Al mismo tiempo, ya que estamos buscando primos menores que
       //el número que especificamos al comienzo, esto tiene el
       //potencial de descartar el número 2, si es ese nuestro número
       //máximo (aunque esto fue descartado en el bucle externo FOR).
       //
       //Por supuesto, esta optimización se hace para cada número
       //que vamos a imprimir, y no solo una vez. Así que si tenemos
       //que comprobar hasta 1003, necesitamos esta optimización para
       //(2/2), (3/2), (4/2), (5/2), (6/2), (7/2),
       //hasta llegar a (1003/2).
       //
       ///
        while (i <= numprimo/2)
        {
         //No hay ningún número par que sea primo, a excepción
         //de 2, así que esta línea lo que hace es ver si el
         //número actual es par, comparando con los pares originales
         //(2 y 0) para evitar tener que comprobar, y acelerar aún
         //más nuestro programa.
         //
         //Hacemos esto al aplicar una máscara AND a 'numeroprimo'
         //con el valor 3 (11 en binario) para quedarnos con los
         //2 bits de menor peso. Si esto es diferente de 2 o de 0,
         //solo puede ser 1 o 3, pero si es 2 o 0, el 'numeroprimo'
         //actual no puede ser primo.
         //
         //Ya que solo imprimimos números cuando 'n' es 0, para el
         //primer número en una serie, que es a partir de 2, siempre
         //se imprimirá este 2, si nuestro número máximo es mayor
         //a 2.
         ///
          if(numprimo&3 == 2 || numprimo&3 == 0)break;








          //Si el número actual, contenido en 'numprimo'
          //el cual termina justo en uno menor que el que
          //introdujimos al inicio; si este número actual
          //controlado por el bucle FOR externo tiene un
          //residuo de división de 0, al usar división o
          //módulo en este caso, entre 'i', que es controlado
          //por este bucle interno WHILE, guardamos el número
          //actual de 'i'.
          //
          ///
           if (numprimo % i == 0)
           {
            n=i;

            //Una vez que encontramos el primer número con una
            //división exacta entre 'numprimo' e 'i', podemos
            //detener este bucle interno WHILE:
            //
            ///
             break;
           }


           //Probamos el siguiente número. Eso significa que
           //'n' contendrá el último valor primo de la secuencia:
           ///
            i++;
        }




    //En el bucle interior WHILE anterior, probamos todos
    //los números hasta la mitad del camino.
    //Aumentamos 'i' en cada repetición del WHILE, y 'numprimo'
    //se mantiene en el mismo valor, a partir de 2, en todo el
    //proceso.
    //
    //Por ejemplo si nuestro número mayor es 4,
    //probamos dividir por:
    //
    //         numprimo % i == 2 % 2 == 0    (imprimimos 2)
    //         numprimo % i == 3 % 2 == 1
    //         numprimo % i == 3 % 3 == 0    (imprimimos 3)
    ///
     if (n == 0)
     {
      printf("\n\t%d\n",numprimo);
      t++;
     }
 }




 printf("Total de primos menores a %d: %d\n", num, t);


 //Función no estándar: sirve para hacer que
 //el sistema espere que el usuario
 //Presione una Tecla para Continuar...
 ///
  system("PAUSE");  



 //Devolvemos el control al sistema:
 ///
  return 0;
}




Cualquier otra duda, puedo tratar de responderla, o corregir cualquier error.



76  Programación / Programación C/C++ / Re: El C/C++ puede reemplazar al ASM? en: 8 Marzo 2012, 17:50 pm
A mí me tardó 6 meses a tiempo completo para aprender apenas lo básico del Ensamblador x86 (con muchos intentos anteriores fallidos de entenderlo), incluyendo arrancar la máquina, leer/escribir discos duros IDE en modo PIO, entrar a modo de 32 bits, y cosas por el estilo.

Aprender a hacer programas de Windows me tomó mucho más tiempo, y aprender apropiada y realmente C entendiendo una parte de las interacciones a nivel de Ensamblador es algo realmente reciente para mí, y todavía me hace falta entender cómo funcionan las excepciones en C++ vistas desde Ensamblador.

En resumen, el lenguaje Ensamblador es diferente para cada procesador distinto, de diferente arquitectura, no es algo de un momento sino que un proceso de preparación y aprendizaje de tiempo completo y extenso. Este lenguaje es una necesidad. C/C++ son el siguiente nivel estandarizado, para que el programador no tenga que encargarse de conocer las instrucciones y optimizaciones de un CPU, y es el compilador el que debe tener la capacidad de crear código de máquina para uno u otro CPU, y a su vez encapsularlo en un formato de archivo apropiado para la máquina (aunque es posible correr código sin absolutamente ningún formato, pero a un nivel más bajo y difuso, pero es perfectamente posible).

Con compiladores como GCC, a veces no solo es posible sino que necesario crear módulos de código objeto hechos en Ensamblador. Estos se usan más tarde en programas de C/C++ usando extern.

Pero en términos generales, siempre hay convenciones que respetar, aunque se trabaje directamente en Ensamblador, por ejemplo, la más común, que es respetar las convenciones de llamada de funciones, sean estas cdecl (programas en C) o stdcall (WinAPI), y llamadas a funciones de C++ con excepciones, etc., que es algo mucho más avanzado y mucho menos conocido con lo que trabajar directamente en Ensamblador (como el caso de MFC).

____________________________________________________


Aquí hay un programa de Windows de 32 bits que hice 100% en Ensamblador, incluyendo las estructuras del formato PE. Todo está hecho en Ensamblador.


http://devel.no-ip.org/tmp/ClockCount.zip

El programa se llama ClockCount.exe, e incluye también un archivo .BAT junto con la versión correcta de NASM para compilarlo por uno mismo. Todo está allí, también el icono del programa incluido como código fuente de Ensamblador.


El código fuente muestra el tipo de instrucciones que se usan en aplicaciones de usuario. No hay obligación de usar extensiones del CPU especiales. Nada especial ni privilegiado.

Siempre es posible emular absolutamente cualquier instrucción que no requiera privilegios especiales, y siempre y cuando sepamos lo que estamos haciendo, con los manuales de Intel/AMD en mano, aunque obviamente nunca van a tener el rendimiento de las instrucciones nativas.

El CPU puede indicar qué extensiones están disponibles usando la instrucción cpuid y otros trucos avanzados, aunque ese es un tema muy extenso, como para otro tema del foro y totalmente cubierto por los manuales de los CPUs.

C y C++ son independientes de Ensamblador, aunque las características de más bajo nivel siempre son fuertemente influenciadas por esos CPUs, tanto así que los mismos manuales y la propia Intel ofrecen un compilador optimizador de C/C++, y las optimizaciones al estilo de C/C++ se dicuten en los propios manuales.

_______________________________________________

Los registros de segmento como el de FS o GS (a aún peor para un sistema operativo bien establecido, DS, ES o SS, o CS -solo indirectamente-) no se usan en lenguajes como C/C++, así que tampoco suelen usarse en las APIs de 32 o 64 bits de los diferentes sistemas.

Aunque a más bajo nivel, todo se vale, por ejemplo estando en 16 bits y activar 32 bits, incluso podemos ejecutar (o incluso emular) el código del BIOS, que no tiene un formato definido.
77  Programación / Programación C/C++ / Re: El C/C++ puede reemplazar al ASM? en: 8 Marzo 2012, 01:25 am
Una máquina de 64 bits con un sistema de 64 bits puede ejecutar de forma nativa por las capacidades intrínsecas del CPU, tanto código de 32 bits como de 64 bits.

El código de 32 bits funcionando bajo modo de 64 bits es "Legacy Mode" (Modo de Compatibilidad), todos los CPUs de 64 bits son capaces nativamente de hacer esto.

Para pruebas de bajo nivel, el código de 64 bits tendría que ejecutarse solo en máquinas de 64 bits, o emular un CPU en modo de 64 bits usando instrucciones de 32 bits (relativamente fácil, aunque trabajoso, si solo nos limitamos a instrucciones determinadas y que preferiblemente no necesiten privilegios especiales -rings, etc.- y necesitamos leer detalladamente los manuales de CPUs de Intel y AMD para la especificación de las instrucciones a emular).

El código/programa de 32 bits puede usarse en un sistema de 64 bits sin problemas, desde el punto de vista simplemente del CPU y la aplicación de usuario.

Si se desea ejecutar un programa en el modo predeterminado y nativo del sistema, habría que crear 2 versiones del mismo programa, de 32 y 64 bits.

Es posible crear código fuente de ensamblador semiportable (usando macros, IFs de precompilador, etc., o algún lenguaje especial, o dejarle simplemente el trabajo a C/C++ si es adecuado para nuestras necesidades, usando un poco de Makefiles para cada versión, o una combinación de lo anterior).
78  Programación / Programación C/C++ / Re: DUDA impresion caracter a caracter con retardo (C) en: 5 Febrero 2012, 02:30 am
Una forma simple y portable, que no depende de ninguna librería, es simplemente usar un bucle vacío con una variable entera que disminuya hasta llegar a cero para "gastar tiempo" y producir una temporización.

El único problema es que el tiempo es muy variable entre diferentes velocidades de CPU, en la pequeña escala (milisegundos o microsegundos), y a veces también a gran escala (décimas de segundo o centésimas de segundo).

Hay que tener cuidado: un valor de la variable muy alto se tardaría demasiado, y un valor muy bajo produciría una temporización demasiado corta para, por ejemplo, simular escribir en un teclado a la velocidad "natural".

Para este problema, puede que esto sea lo más simple, ya que es capaz de esperar menos de 1 segundo entre caracteres, que es más natural para alguien que escribe relativamente rápido.

Lo realmente ideal es usar las funciones de temporización de cada sistema operativo, y si se usa C (que internamente también usa las librerías de cada sistema en que está implementado) lo normal es escribir o usar una función existente que permita esperar milisegundos en lugar de segundos (1000 milisegundos son 1 segundo), porque eso es lo verdaderamente práctico, útil y necesario, en programación de producción.

Código:
#include <stdio.h>
#include <time.h>


void delayerloop(void);


int main(void)
{
 //Declarar nuestro contador de bucle de cadena,
 //nuestra cadena, y la longitud de la cadena:
 ///
  int n;
  char s[]="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  int slen=sizeof(s);


 //Mostrar cada carácter con un retardo.
  for(n=0;n<slen;n++)
  {
   //Retardar:
   ///
    delayerloop();

   //Poner un carácter en 'stdout' (salida estándar o consola
   //de texto en este caso):
   ///
    putchar(s[n]);


   //Forzar escribir el carácter ya. Si esto no se usa, el
   //carácter no se mostrará de manera inmediata:
   ///
    fflush(stdout);
  }

  return 0;
}




//Esta función se vale de un bucle con un contador
//expresado con números hexadecimales para provocar un
//pequeño retraso, sin usar ninguna librería.
//
//Esto es muy simple y portable en teoría, pero el tiempo
//que se tardará en cuestión dependerá de la velocidad
//de cada CPU en el que se corra, que puede ser más o menos
//lento.
///
void delayerloop(void)
{
 //Esto se puede cambiar al valor que deseemos
 ///
  register long x=0x8FFFFFF;


 //Efectuar la temporización:
 ///
  while(x--);
}
79  Programación / Programación C/C++ / Re: Problema al abrir binario en C en: 5 Febrero 2012, 00:38 am
Este programa funciona bien para esa tarea hasta ahora. Toda la información está en los comentarios.

Simplemente usa una función más, que se limita a buscar el carácter '\r' o '\n' y lo modifica directamente en la cadena original, cambiándolo por un carácter nulo '\0'. Ni siquiera se necesita malloc para ese pequeño ajuste de cadena.


Código:
#include <stdio.h>   //printf, fopen, fgets, fclose
#include <stdlib.h>  //exit
#include <string.h>  //strlen



int openbin(void);
void striptrail(char *, size_t);


int main(void)
{
 openbin();
 return 0;
}



int openbin(void)
{
 //Definimos el manejador de archivo y nuestro búfer de cadena:
 ///
  FILE *pfile;
  char namebin[50];


 //Mostramos un mensaje pidiendo un nombre de archivo
 //y leemos dicha entrada de usuario con fgets:
 ///
   printf("Nombre de archivo: ");
   fgets(namebin,sizeof(namebin), stdin);


 //A continuación mostramos el tamaño de la cadena leída
 //con fgets, y luego aplicamos la función para eliminar
 //nuevas líneas/retornos de carro, para preparar la cadena
 //y aprovechamos a mostrar la nueva longitud (si es que se
 //ajustó):
 ///
   printf("Longitud de cadena sin procesar: %d\n",strlen(namebin));

 //Le pasamos directamente el nombre del búfer de cadena, porque
 //este es ya en sí un puntero directo al búfer de cadena:
 ///
   striptrail(namebin, sizeof(namebin));
   printf("Longitud de cadena procesada con 'striptrail': %d\n\n",strlen(namebin));



 //Tratamos de abrir el archivo. Si no se puede abrir, mostramos
 //un error; si se abrió mostramos mensaje de éxito. Después
 //cerramos el manejador de archivo y salimos al sistema.
 ///
   if ((pfile = fopen(namebin, "rb"))== NULL)
   {
    printf("ERROR: No se pudo abrir el archivo.\n");
    exit(1);
   }
   printf("Archivo abierto exitosamente.\n");
   fclose(pfile);

 return 0;
}






//Esta función simple está pensada para usarse con fgets.
//
//Lo que hace es tomar un puntero a una cadena, y el tamaño
//esperado de la cadena.
//
//Entonces busca un carácter de retorno de carro '\n' o nueva
//línea '\n', introducidos por fgets al presionar ENTER.
//
//NOTA: Si no se encuentra ninguno de esos caracteres, entonces
//      el último carácter, al final del búfer de cadena de texto,
//      se reemplaza incondicionalmente por un byte nulo '\0' para
//      terminar la cadena ASCIIZ, por seguridad.
//
//      Es por eso que esta función está diseñada para trabajar con
//      fgets u otro código que guarde '\r' o '\n' al final de la
//      cadena esperada.
//
///
void striptrail(char * tostrip, size_t sizze)
{
 //Tratamos de aplicar optimización de uso de
 //registros de CPU, para mayor velocidad para
 //el contador del bucle:
 ///
   register size_t x=0;



 //Recorremos toda la cadena:
 ///
   for(x=0;x<sizze;x++)
   {
    //Si el carácter que estamos leyendo actualmente es
    //regreso de carro o nueva línea, sabemos que tenemos
    //que cambiarlo por el byte nulo '\0' para indicar
    //el fin de la cadena. Nos detenemos tan pronto como
    //encontremos uno de esos caracteres, regresando de
    //la función de inmediato, para evitar seguir escaneando
    //la cadena si el objetivo ya está cumplido:
    ///
      if(tostrip[x]=='\r' || tostrip[x]=='\n' ||  tostrip[x]=='\0')
      {
       tostrip[x]='\0';
       return;
      }
   }



 //Si x tiene el valor del tamaño esperado del búfer,
 //reemplazamos el carácter en esa posición con un byte
 //nulo para terminar la cadena. Esta línea se puede dejar
 //o eliminar dependiendo de cómo funcione nuestro programa,
 //y si estamos totalmente seguros de que no necesitamos
 //esta "característica de seguridad" (pero vital en este programa):
 ///
  tostrip[x]='\0';
}

80  Seguridad Informática / Análisis y Diseño de Malware / Re: Como determinar cuando ha sido modificada la AddressOfEntryPoint de un PE en: 29 Abril 2011, 01:49 am
Muchos programas (la mayoría) tienen un valor de 0 en el checksum de la cabecera PE. Solo a los drivers, DLLs y ejecutables de sistema se les requiere tener un checksum válido.

Por eso depender de este campo no es bueno para determinar un cambio en el ejecutable. Sería mejor calcular desde el principio un checksum global con MD5 o SHA-1 a todo el archivo .EXE, y guardarlo como clave en un lugar seguro, no en el .EXE, para volver a comparar cuando se necesite.
Páginas: 1 2 3 4 5 6 7 [8] 9 10
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines